Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
Conflicts: core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java features/features.xml tools/test/cells/office Change-Id: I08e6d7c6a0bdaae072dd50ff7ac36d94f16d77e1
Showing
151 changed files
with
9587 additions
and
2351 deletions
apps/calendar/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>onos-apps</artifactId> | ||
10 | + <version>1.0.0-SNAPSHOT</version> | ||
11 | + <relativePath>../pom.xml</relativePath> | ||
12 | + </parent> | ||
13 | + | ||
14 | + <artifactId>onos-app-calendar</artifactId> | ||
15 | + <packaging>bundle</packaging> | ||
16 | + | ||
17 | + <description>ONOS simple calendaring REST interface for intents</description> | ||
18 | + | ||
19 | + <properties> | ||
20 | + <web.context>/onos/calendar</web.context> | ||
21 | + </properties> | ||
22 | + | ||
23 | + <dependencies> | ||
24 | + <dependency> | ||
25 | + <groupId>org.onlab.onos</groupId> | ||
26 | + <artifactId>onlab-rest</artifactId> | ||
27 | + <version>${project.version}</version> | ||
28 | + </dependency> | ||
29 | + | ||
30 | + <dependency> | ||
31 | + <groupId>com.sun.jersey</groupId> | ||
32 | + <artifactId>jersey-servlet</artifactId> | ||
33 | + </dependency> | ||
34 | + <dependency> | ||
35 | + <groupId>com.sun.jersey.jersey-test-framework</groupId> | ||
36 | + <artifactId>jersey-test-framework-core</artifactId> | ||
37 | + <version>1.18.1</version> | ||
38 | + <scope>test</scope> | ||
39 | + </dependency> | ||
40 | + <dependency> | ||
41 | + <groupId>com.sun.jersey.jersey-test-framework</groupId> | ||
42 | + <artifactId>jersey-test-framework-grizzly2</artifactId> | ||
43 | + <version>1.18.1</version> | ||
44 | + <scope>test</scope> | ||
45 | + </dependency> | ||
46 | + <dependency> | ||
47 | + <groupId>org.osgi</groupId> | ||
48 | + <artifactId>org.osgi.core</artifactId> | ||
49 | + </dependency> | ||
50 | + </dependencies> | ||
51 | + | ||
52 | + <build> | ||
53 | + <plugins> | ||
54 | + <plugin> | ||
55 | + <groupId>org.apache.felix</groupId> | ||
56 | + <artifactId>maven-bundle-plugin</artifactId> | ||
57 | + <extensions>true</extensions> | ||
58 | + <configuration> | ||
59 | + <instructions> | ||
60 | + <_wab>src/main/webapp/</_wab> | ||
61 | + <Bundle-SymbolicName> | ||
62 | + ${project.groupId}.${project.artifactId} | ||
63 | + </Bundle-SymbolicName> | ||
64 | + <Import-Package> | ||
65 | + org.osgi.framework, | ||
66 | + javax.ws.rs,javax.ws.rs.core, | ||
67 | + com.sun.jersey.api.core, | ||
68 | + com.sun.jersey.spi.container.servlet, | ||
69 | + com.sun.jersey.server.impl.container.servlet, | ||
70 | + org.onlab.packet.*, | ||
71 | + org.onlab.rest.*, | ||
72 | + org.onlab.onos.* | ||
73 | + </Import-Package> | ||
74 | + <Web-ContextPath>${web.context}</Web-ContextPath> | ||
75 | + </instructions> | ||
76 | + </configuration> | ||
77 | + </plugin> | ||
78 | + </plugins> | ||
79 | + </build> | ||
80 | + | ||
81 | +</project> |
1 | +package org.onlab.onos.calendar; | ||
2 | + | ||
3 | +import org.onlab.onos.net.ConnectPoint; | ||
4 | +import org.onlab.onos.net.DeviceId; | ||
5 | +import org.onlab.onos.net.intent.IntentService; | ||
6 | +import org.onlab.rest.BaseResource; | ||
7 | + | ||
8 | +import javax.ws.rs.POST; | ||
9 | +import javax.ws.rs.Path; | ||
10 | +import javax.ws.rs.PathParam; | ||
11 | +import javax.ws.rs.core.Response; | ||
12 | +import java.net.URI; | ||
13 | + | ||
14 | +import static org.onlab.onos.net.PortNumber.portNumber; | ||
15 | + | ||
16 | +/** | ||
17 | + * Web resource for triggering calendared intents. | ||
18 | + */ | ||
19 | +@Path("intent") | ||
20 | +public class BandwidthCalendarResource extends BaseResource { | ||
21 | + | ||
22 | + @POST | ||
23 | + @Path("{src}/{dst}/{srcPort}/{dstPort}/{bandwidth}") | ||
24 | + public Response createIntent(@PathParam("src") String src, | ||
25 | + @PathParam("dst") String dst, | ||
26 | + @PathParam("srcPort") String srcPort, | ||
27 | + @PathParam("dstPort") String dstPort, | ||
28 | + @PathParam("bandwidth") String bandwidth) { | ||
29 | + // TODO: implement calls to intent framework | ||
30 | + IntentService service = get(IntentService.class); | ||
31 | + | ||
32 | + ConnectPoint srcPoint = new ConnectPoint(deviceId(src), portNumber(srcPort)); | ||
33 | + ConnectPoint dstPoint = new ConnectPoint(deviceId(dst), portNumber(dstPort)); | ||
34 | + | ||
35 | + return Response.ok("Yo! We got src=" + srcPoint + "; dst=" + dstPoint + | ||
36 | + "; bw=" + bandwidth + "; intent service " + service).build(); | ||
37 | + } | ||
38 | + | ||
39 | + private DeviceId deviceId(String dpid) { | ||
40 | + return DeviceId.deviceId(URI.create("of:" + dpid)); | ||
41 | + } | ||
42 | + | ||
43 | +} |
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" | ||
3 | + xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" | ||
4 | + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" | ||
5 | + id="ONOS" version="2.5"> | ||
6 | + <display-name>ONOS GUI</display-name> | ||
7 | + | ||
8 | + <servlet> | ||
9 | + <servlet-name>JAX-RS Service</servlet-name> | ||
10 | + <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> | ||
11 | + <init-param> | ||
12 | + <param-name>com.sun.jersey.config.property.packages</param-name> | ||
13 | + <param-value>org.onlab.onos.calendar</param-value> | ||
14 | + </init-param> | ||
15 | + <load-on-startup>10</load-on-startup> | ||
16 | + </servlet> | ||
17 | + | ||
18 | + <servlet-mapping> | ||
19 | + <servlet-name>JAX-RS Service</servlet-name> | ||
20 | + <url-pattern>/rs/*</url-pattern> | ||
21 | + </servlet-mapping> | ||
22 | + | ||
23 | +</web-app> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -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 | } | ... | ... |
... | @@ -25,6 +25,7 @@ | ... | @@ -25,6 +25,7 @@ |
25 | <module>proxyarp</module> | 25 | <module>proxyarp</module> |
26 | <module>config</module> | 26 | <module>config</module> |
27 | <module>sdnip</module> | 27 | <module>sdnip</module> |
28 | + <module>calendar</module> | ||
28 | </modules> | 29 | </modules> |
29 | 30 | ||
30 | <properties> | 31 | <properties> | ... | ... |
... | @@ -36,6 +36,36 @@ | ... | @@ -36,6 +36,36 @@ |
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 | + | ||
45 | + <dependency> | ||
46 | + <groupId>org.onlab.onos</groupId> | ||
47 | + <artifactId>onlab-misc</artifactId> | ||
48 | + </dependency> | ||
49 | + | ||
50 | + <dependency> | ||
51 | + <groupId>org.onlab.onos</groupId> | ||
52 | + <artifactId>onos-cli</artifactId> | ||
53 | + <version>${project.version}</version> | ||
54 | + </dependency> | ||
55 | + <dependency> | ||
56 | + <groupId>org.apache.karaf.shell</groupId> | ||
57 | + <artifactId>org.apache.karaf.shell.console</artifactId> | ||
58 | + </dependency> | ||
59 | + <dependency> | ||
60 | + <groupId>org.osgi</groupId> | ||
61 | + <artifactId>org.osgi.core</artifactId> | ||
62 | + </dependency> | ||
63 | + | ||
64 | + <dependency> | ||
65 | + <groupId>org.easymock</groupId> | ||
66 | + <artifactId>easymock</artifactId> | ||
67 | + <scope>test</scope> | ||
68 | + </dependency> | ||
39 | </dependencies> | 69 | </dependencies> |
40 | 70 | ||
41 | </project> | 71 | </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 radix tree. | ||
66 | + // The key in this tree is the binary string of prefix of the route. | ||
67 | + private InvertedRadixTree<RouteEntry> bgpRoutes; | ||
68 | + | ||
69 | + // Stores all incoming route updates in a queue. | ||
70 | + private BlockingQueue<RouteUpdate> routeUpdates; | ||
71 | + | ||
72 | + // The Ip4Address is the next hop address of each route update. | ||
73 | + private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp; | ||
74 | + private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents; | ||
75 | + | ||
76 | + private IntentService intentService; | ||
77 | + //private IProxyArpService proxyArp; | ||
78 | + private HostService hostService; | ||
79 | + private SdnIpConfigService configInfoService; | ||
80 | + private InterfaceService interfaceService; | ||
81 | + | ||
82 | + private ExecutorService bgpUpdatesExecutor; | ||
83 | + private ExecutorService bgpIntentsSynchronizerExecutor; | ||
84 | + | ||
85 | + // TODO temporary | ||
86 | + private int intentId = Integer.MAX_VALUE / 2; | ||
87 | + | ||
88 | + // | ||
89 | + // State to deal with SDN-IP Leader election and pushing Intents | ||
90 | + // | ||
91 | + private Semaphore intentsSynchronizerSemaphore = new Semaphore(0); | ||
92 | + private volatile boolean isElectedLeader = false; | ||
93 | + private volatile boolean isActivatedLeader = false; | ||
94 | + | ||
95 | + // For routes announced by local BGP daemon in SDN network, | ||
96 | + // the next hop will be 0.0.0.0. | ||
97 | + public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0"); | ||
98 | + | ||
99 | + /** | ||
100 | + * Class constructor. | ||
101 | + * | ||
102 | + * @param intentService the intent service | ||
103 | + * @param hostService the host service | ||
104 | + * @param configInfoService the configuration service | ||
105 | + * @param interfaceService the interface service | ||
106 | + */ | ||
107 | + public Router(IntentService intentService, HostService hostService, | ||
108 | + SdnIpConfigService configInfoService, InterfaceService interfaceService) { | ||
109 | + | ||
110 | + this.intentService = intentService; | ||
111 | + this.hostService = hostService; | ||
112 | + this.configInfoService = configInfoService; | ||
113 | + this.interfaceService = interfaceService; | ||
114 | + | ||
115 | + bgpRoutes = new ConcurrentInvertedRadixTree<>( | ||
116 | + new DefaultByteArrayNodeFactory()); | ||
117 | + routeUpdates = new LinkedBlockingQueue<>(); | ||
118 | + routesWaitingOnArp = Multimaps.synchronizedSetMultimap( | ||
119 | + HashMultimap.<IpAddress, RouteEntry>create()); | ||
120 | + pushedRouteIntents = new ConcurrentHashMap<>(); | ||
121 | + | ||
122 | + bgpUpdatesExecutor = Executors.newSingleThreadExecutor( | ||
123 | + new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build()); | ||
124 | + bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor( | ||
125 | + new ThreadFactoryBuilder() | ||
126 | + .setNameFormat("bgp-intents-synchronizer-%d").build()); | ||
127 | + | ||
128 | + this.hostService.addListener(new InternalHostListener()); | ||
129 | + } | ||
130 | + | ||
131 | + /** | ||
132 | + * Starts the Router. | ||
133 | + */ | ||
134 | + public void start() { | ||
135 | + | ||
136 | + bgpUpdatesExecutor.execute(new Runnable() { | ||
137 | + @Override | ||
138 | + public void run() { | ||
139 | + doUpdatesThread(); | ||
140 | + } | ||
141 | + }); | ||
142 | + | ||
143 | + bgpIntentsSynchronizerExecutor.execute(new Runnable() { | ||
144 | + @Override | ||
145 | + public void run() { | ||
146 | + doIntentSynchronizationThread(); | ||
147 | + } | ||
148 | + }); | ||
149 | + } | ||
150 | + | ||
151 | + //@Override TODO hook this up to something | ||
152 | + public void leaderChanged(boolean isLeader) { | ||
153 | + log.debug("Leader changed: {}", isLeader); | ||
154 | + | ||
155 | + if (!isLeader) { | ||
156 | + this.isElectedLeader = false; | ||
157 | + this.isActivatedLeader = false; | ||
158 | + return; // Nothing to do | ||
159 | + } | ||
160 | + this.isActivatedLeader = false; | ||
161 | + this.isElectedLeader = true; | ||
162 | + | ||
163 | + // | ||
164 | + // Tell the Intents Synchronizer thread to start the synchronization | ||
165 | + // | ||
166 | + intentsSynchronizerSemaphore.release(); | ||
167 | + } | ||
168 | + | ||
169 | + @Override | ||
170 | + public void update(RouteUpdate routeUpdate) { | ||
171 | + log.debug("Received new route Update: {}", routeUpdate); | ||
172 | + | ||
173 | + try { | ||
174 | + routeUpdates.put(routeUpdate); | ||
175 | + } catch (InterruptedException e) { | ||
176 | + log.debug("Interrupted while putting on routeUpdates queue", e); | ||
177 | + Thread.currentThread().interrupt(); | ||
178 | + } | ||
179 | + } | ||
180 | + | ||
181 | + /** | ||
182 | + * Thread for Intent Synchronization. | ||
183 | + */ | ||
184 | + private void doIntentSynchronizationThread() { | ||
185 | + boolean interrupted = false; | ||
186 | + try { | ||
187 | + while (!interrupted) { | ||
188 | + try { | ||
189 | + intentsSynchronizerSemaphore.acquire(); | ||
190 | + // | ||
191 | + // Drain all permits, because a single synchronization is | ||
192 | + // sufficient. | ||
193 | + // | ||
194 | + intentsSynchronizerSemaphore.drainPermits(); | ||
195 | + } catch (InterruptedException e) { | ||
196 | + log.debug("Interrupted while waiting to become " + | ||
197 | + "Intent Synchronization leader"); | ||
198 | + interrupted = true; | ||
199 | + break; | ||
200 | + } | ||
201 | + syncIntents(); | ||
202 | + } | ||
203 | + } finally { | ||
204 | + if (interrupted) { | ||
205 | + Thread.currentThread().interrupt(); | ||
206 | + } | ||
207 | + } | ||
208 | + } | ||
209 | + | ||
210 | + /** | ||
211 | + * Thread for handling route updates. | ||
212 | + */ | ||
213 | + private void doUpdatesThread() { | ||
214 | + boolean interrupted = false; | ||
215 | + try { | ||
216 | + while (!interrupted) { | ||
217 | + try { | ||
218 | + RouteUpdate update = routeUpdates.take(); | ||
219 | + switch (update.type()) { | ||
220 | + case UPDATE: | ||
221 | + processRouteAdd(update.routeEntry()); | ||
222 | + break; | ||
223 | + case DELETE: | ||
224 | + processRouteDelete(update.routeEntry()); | ||
225 | + break; | ||
226 | + default: | ||
227 | + log.error("Unknown update Type: {}", update.type()); | ||
228 | + break; | ||
229 | + } | ||
230 | + } catch (InterruptedException e) { | ||
231 | + log.debug("Interrupted while taking from updates queue", e); | ||
232 | + interrupted = true; | ||
233 | + } catch (Exception e) { | ||
234 | + log.debug("exception", e); | ||
235 | + } | ||
236 | + } | ||
237 | + } finally { | ||
238 | + if (interrupted) { | ||
239 | + Thread.currentThread().interrupt(); | ||
240 | + } | ||
241 | + } | ||
242 | + } | ||
243 | + | ||
244 | + /** | ||
245 | + * Performs Intents Synchronization between the internally stored Route | ||
246 | + * Intents and the installed Route Intents. | ||
247 | + */ | ||
248 | + private void syncIntents() { | ||
249 | + synchronized (this) { | ||
250 | + if (!isElectedLeader) { | ||
251 | + return; // Nothing to do: not the leader anymore | ||
252 | + } | ||
253 | + log.debug("Syncing SDN-IP Route Intents..."); | ||
254 | + | ||
255 | + Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents = | ||
256 | + new HashMap<>(); | ||
257 | + | ||
258 | + // | ||
259 | + // Fetch all intents, and classify the Multi-Point-to-Point Intents | ||
260 | + // based on the matching prefix. | ||
261 | + // | ||
262 | + for (Intent intent : intentService.getIntents()) { | ||
263 | + // | ||
264 | + // TODO: Ignore all intents that are not installed by | ||
265 | + // the SDN-IP application. | ||
266 | + // | ||
267 | + if (!(intent instanceof MultiPointToSinglePointIntent)) { | ||
268 | + continue; | ||
269 | + } | ||
270 | + MultiPointToSinglePointIntent mp2pIntent = | ||
271 | + (MultiPointToSinglePointIntent) intent; | ||
272 | + /*Match match = mp2pIntent.getMatch(); | ||
273 | + if (!(match instanceof PacketMatch)) { | ||
274 | + continue; | ||
275 | + } | ||
276 | + PacketMatch packetMatch = (PacketMatch) match; | ||
277 | + Ip4Prefix prefix = packetMatch.getDstIpAddress(); | ||
278 | + if (prefix == null) { | ||
279 | + continue; | ||
280 | + } | ||
281 | + fetchedIntents.put(prefix, mp2pIntent);*/ | ||
282 | + for (Criterion criterion : mp2pIntent.selector().criteria()) { | ||
283 | + if (criterion.type() == Type.IPV4_DST) { | ||
284 | + IPCriterion ipCriterion = (IPCriterion) criterion; | ||
285 | + fetchedIntents.put(ipCriterion.ip(), mp2pIntent); | ||
286 | + } | ||
287 | + } | ||
288 | + | ||
289 | + } | ||
290 | + | ||
291 | + // | ||
292 | + // Compare for each prefix the local IN-MEMORY Intents with the | ||
293 | + // FETCHED Intents: | ||
294 | + // - If the IN-MEMORY Intent is same as the FETCHED Intent, store | ||
295 | + // the FETCHED Intent in the local memory (i.e., override the | ||
296 | + // IN-MEMORY Intent) to preserve the original Intent ID | ||
297 | + // - if the IN-MEMORY Intent is not same as the FETCHED Intent, | ||
298 | + // delete the FETCHED Intent, and push/install the IN-MEMORY | ||
299 | + // Intent. | ||
300 | + // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED | ||
301 | + // Intent for same prefix, then push/install the IN-MEMORY | ||
302 | + // Intent. | ||
303 | + // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY | ||
304 | + // Intent for same prefix, then delete/withdraw the FETCHED | ||
305 | + // Intent. | ||
306 | + // | ||
307 | + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> | ||
308 | + storeInMemoryIntents = new LinkedList<>(); | ||
309 | + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> | ||
310 | + addIntents = new LinkedList<>(); | ||
311 | + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> | ||
312 | + deleteIntents = new LinkedList<>(); | ||
313 | + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : | ||
314 | + pushedRouteIntents.entrySet()) { | ||
315 | + IpPrefix prefix = entry.getKey(); | ||
316 | + MultiPointToSinglePointIntent inMemoryIntent = | ||
317 | + entry.getValue(); | ||
318 | + MultiPointToSinglePointIntent fetchedIntent = | ||
319 | + fetchedIntents.get(prefix); | ||
320 | + | ||
321 | + if (fetchedIntent == null) { | ||
322 | + // | ||
323 | + // No FETCHED Intent for same prefix: push the IN-MEMORY | ||
324 | + // Intent. | ||
325 | + // | ||
326 | + addIntents.add(Pair.of(prefix, inMemoryIntent)); | ||
327 | + continue; | ||
328 | + } | ||
329 | + | ||
330 | + // | ||
331 | + // If IN-MEMORY Intent is same as the FETCHED Intent, | ||
332 | + // store the FETCHED Intent in the local memory. | ||
333 | + // | ||
334 | + if (compareMultiPointToSinglePointIntents(inMemoryIntent, | ||
335 | + fetchedIntent)) { | ||
336 | + storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent)); | ||
337 | + } else { | ||
338 | + // | ||
339 | + // The IN-MEMORY Intent is not same as the FETCHED Intent, | ||
340 | + // hence delete the FETCHED Intent, and install the | ||
341 | + // IN-MEMORY Intent. | ||
342 | + // | ||
343 | + deleteIntents.add(Pair.of(prefix, fetchedIntent)); | ||
344 | + addIntents.add(Pair.of(prefix, inMemoryIntent)); | ||
345 | + } | ||
346 | + fetchedIntents.remove(prefix); | ||
347 | + } | ||
348 | + | ||
349 | + // | ||
350 | + // Any remaining FETCHED Intents have to be deleted/withdrawn | ||
351 | + // | ||
352 | + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : | ||
353 | + fetchedIntents.entrySet()) { | ||
354 | + IpPrefix prefix = entry.getKey(); | ||
355 | + MultiPointToSinglePointIntent fetchedIntent = entry.getValue(); | ||
356 | + deleteIntents.add(Pair.of(prefix, fetchedIntent)); | ||
357 | + } | ||
358 | + | ||
359 | + // | ||
360 | + // Perform the actions: | ||
361 | + // 1. Store in memory fetched intents that are same. Can be done | ||
362 | + // even if we are not the leader anymore | ||
363 | + // 2. Delete intents: check if the leader before each operation | ||
364 | + // 3. Add intents: check if the leader before each operation | ||
365 | + // | ||
366 | + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair : | ||
367 | + storeInMemoryIntents) { | ||
368 | + IpPrefix prefix = pair.getLeft(); | ||
369 | + MultiPointToSinglePointIntent intent = pair.getRight(); | ||
370 | + log.debug("Intent synchronization: updating in-memory " + | ||
371 | + "Intent for prefix: {}", prefix); | ||
372 | + pushedRouteIntents.put(prefix, intent); | ||
373 | + } | ||
374 | + // | ||
375 | + isActivatedLeader = true; // Allow push of Intents | ||
376 | + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair : | ||
377 | + deleteIntents) { | ||
378 | + IpPrefix prefix = pair.getLeft(); | ||
379 | + MultiPointToSinglePointIntent intent = pair.getRight(); | ||
380 | + if (!isElectedLeader) { | ||
381 | + isActivatedLeader = false; | ||
382 | + return; | ||
383 | + } | ||
384 | + log.debug("Intent synchronization: deleting Intent for " + | ||
385 | + "prefix: {}", prefix); | ||
386 | + intentService.withdraw(intent); | ||
387 | + } | ||
388 | + // | ||
389 | + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair : | ||
390 | + addIntents) { | ||
391 | + IpPrefix prefix = pair.getLeft(); | ||
392 | + MultiPointToSinglePointIntent intent = pair.getRight(); | ||
393 | + if (!isElectedLeader) { | ||
394 | + isActivatedLeader = false; | ||
395 | + return; | ||
396 | + } | ||
397 | + log.debug("Intent synchronization: adding Intent for " + | ||
398 | + "prefix: {}", prefix); | ||
399 | + intentService.submit(intent); | ||
400 | + } | ||
401 | + if (!isElectedLeader) { | ||
402 | + isActivatedLeader = false; | ||
403 | + } | ||
404 | + log.debug("Syncing SDN-IP routes completed."); | ||
405 | + } | ||
406 | + } | ||
407 | + | ||
408 | + /** | ||
409 | + * Compares two Multi-point to Single Point Intents whether they represent | ||
410 | + * same logical intention. | ||
411 | + * | ||
412 | + * @param intent1 the first Intent to compare | ||
413 | + * @param intent2 the second Intent to compare | ||
414 | + * @return true if both Intents represent same logical intention, otherwise | ||
415 | + * false | ||
416 | + */ | ||
417 | + private boolean compareMultiPointToSinglePointIntents( | ||
418 | + MultiPointToSinglePointIntent intent1, | ||
419 | + MultiPointToSinglePointIntent intent2) { | ||
420 | + /*Match match1 = intent1.getMatch(); | ||
421 | + Match match2 = intent2.getMatch(); | ||
422 | + Action action1 = intent1.getAction(); | ||
423 | + Action action2 = intent2.getAction(); | ||
424 | + Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts(); | ||
425 | + Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts(); | ||
426 | + SwitchPort egressPort1 = intent1.getEgressPort(); | ||
427 | + SwitchPort egressPort2 = intent2.getEgressPort(); | ||
428 | + | ||
429 | + return Objects.equal(match1, match2) && | ||
430 | + Objects.equal(action1, action2) && | ||
431 | + Objects.equal(egressPort1, egressPort2) && | ||
432 | + Objects.equal(ingressPorts1, ingressPorts2);*/ | ||
433 | + return Objects.equal(intent1.selector(), intent2.selector()) && | ||
434 | + Objects.equal(intent1.treatment(), intent2.treatment()) && | ||
435 | + Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) && | ||
436 | + Objects.equal(intent1.egressPoint(), intent2.egressPoint()); | ||
437 | + } | ||
438 | + | ||
439 | + /** | ||
440 | + * Processes adding a route entry. | ||
441 | + * <p/> | ||
442 | + * Put new route entry into the radix tree. If there was an existing | ||
443 | + * next hop for this prefix, but the next hop was different, then execute | ||
444 | + * deleting old route entry. If the next hop is the SDN domain, we do not | ||
445 | + * handle it at the moment. Otherwise, execute adding a route. | ||
446 | + * | ||
447 | + * @param routeEntry the route entry to add | ||
448 | + */ | ||
449 | + protected void processRouteAdd(RouteEntry routeEntry) { | ||
450 | + synchronized (this) { | ||
451 | + log.debug("Processing route add: {}", routeEntry); | ||
452 | + | ||
453 | + IpPrefix prefix = routeEntry.prefix(); | ||
454 | + IpAddress nextHop = null; | ||
455 | + RouteEntry foundRouteEntry = | ||
456 | + bgpRoutes.put(RouteEntry.createBinaryString(prefix), | ||
457 | + routeEntry); | ||
458 | + if (foundRouteEntry != null) { | ||
459 | + nextHop = foundRouteEntry.nextHop(); | ||
460 | + } | ||
461 | + | ||
462 | + if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) { | ||
463 | + // There was an existing nexthop for this prefix. This update | ||
464 | + // supersedes that, so we need to remove the old flows for this | ||
465 | + // prefix from the switches | ||
466 | + executeRouteDelete(routeEntry); | ||
467 | + } | ||
468 | + if (nextHop != null && nextHop.equals(routeEntry.nextHop())) { | ||
469 | + return; | ||
470 | + } | ||
471 | + | ||
472 | + if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) { | ||
473 | + // Route originated by SDN domain | ||
474 | + // We don't handle these at the moment | ||
475 | + log.debug("Own route {} to {}", | ||
476 | + routeEntry.prefix(), routeEntry.nextHop()); | ||
477 | + return; | ||
478 | + } | ||
479 | + | ||
480 | + executeRouteAdd(routeEntry); | ||
481 | + } | ||
482 | + } | ||
483 | + | ||
484 | + /** | ||
485 | + * Executes adding a route entry. | ||
486 | + * <p/> | ||
487 | + * Find out the egress Interface and MAC address of next hop router for | ||
488 | + * this route entry. If the MAC address can not be found in ARP cache, | ||
489 | + * then this prefix will be put in routesWaitingOnArp queue. Otherwise, | ||
490 | + * new route intent will be created and installed. | ||
491 | + * | ||
492 | + * @param routeEntry the route entry to add | ||
493 | + */ | ||
494 | + private void executeRouteAdd(RouteEntry routeEntry) { | ||
495 | + log.debug("Executing route add: {}", routeEntry); | ||
496 | + | ||
497 | + // See if we know the MAC address of the next hop | ||
498 | + //MacAddress nextHopMacAddress = | ||
499 | + //proxyArp.getMacAddress(routeEntry.getNextHop()); | ||
500 | + MacAddress nextHopMacAddress = null; | ||
501 | + Set<Host> hosts = hostService.getHostsByIp( | ||
502 | + routeEntry.nextHop().toPrefix()); | ||
503 | + if (!hosts.isEmpty()) { | ||
504 | + // TODO how to handle if multiple hosts are returned? | ||
505 | + nextHopMacAddress = hosts.iterator().next().mac(); | ||
506 | + } | ||
507 | + | ||
508 | + if (nextHopMacAddress == null) { | ||
509 | + routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry); | ||
510 | + //proxyArp.sendArpRequest(routeEntry.getNextHop(), this, true); | ||
511 | + // TODO maybe just do this for every prefix anyway | ||
512 | + hostService.startMonitoringIp(routeEntry.nextHop()); | ||
513 | + return; | ||
514 | + } | ||
515 | + | ||
516 | + addRouteIntentToNextHop(routeEntry.prefix(), | ||
517 | + routeEntry.nextHop(), | ||
518 | + nextHopMacAddress); | ||
519 | + } | ||
520 | + | ||
521 | + /** | ||
522 | + * Adds a route intent given a prefix and a next hop IP address. This | ||
523 | + * method will find the egress interface for the intent. | ||
524 | + * | ||
525 | + * @param prefix IP prefix of the route to add | ||
526 | + * @param nextHopIpAddress IP address of the next hop | ||
527 | + * @param nextHopMacAddress MAC address of the next hop | ||
528 | + */ | ||
529 | + private void addRouteIntentToNextHop(IpPrefix prefix, | ||
530 | + IpAddress nextHopIpAddress, | ||
531 | + MacAddress nextHopMacAddress) { | ||
532 | + | ||
533 | + // Find the attachment point (egress interface) of the next hop | ||
534 | + Interface egressInterface; | ||
535 | + if (configInfoService.getBgpPeers().containsKey(nextHopIpAddress)) { | ||
536 | + // Route to a peer | ||
537 | + log.debug("Route to peer {}", nextHopIpAddress); | ||
538 | + BgpPeer peer = | ||
539 | + configInfoService.getBgpPeers().get(nextHopIpAddress); | ||
540 | + egressInterface = | ||
541 | + interfaceService.getInterface(peer.connectPoint()); | ||
542 | + } else { | ||
543 | + // Route to non-peer | ||
544 | + log.debug("Route to non-peer {}", nextHopIpAddress); | ||
545 | + egressInterface = | ||
546 | + interfaceService.getMatchingInterface(nextHopIpAddress); | ||
547 | + if (egressInterface == null) { | ||
548 | + log.warn("No outgoing interface found for {}", | ||
549 | + nextHopIpAddress); | ||
550 | + return; | ||
551 | + } | ||
552 | + } | ||
553 | + | ||
554 | + doAddRouteIntent(prefix, egressInterface, nextHopMacAddress); | ||
555 | + } | ||
556 | + | ||
557 | + /** | ||
558 | + * Installs a route intent for a prefix. | ||
559 | + * <p/> | ||
560 | + * Intent will match dst IP prefix and rewrite dst MAC address at all other | ||
561 | + * border switches, then forward packets according to dst MAC address. | ||
562 | + * | ||
563 | + * @param prefix IP prefix from route | ||
564 | + * @param egressInterface egress Interface connected to next hop router | ||
565 | + * @param nextHopMacAddress MAC address of next hop router | ||
566 | + */ | ||
567 | + private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface, | ||
568 | + MacAddress nextHopMacAddress) { | ||
569 | + log.debug("Adding intent for prefix {}, next hop mac {}", | ||
570 | + prefix, nextHopMacAddress); | ||
571 | + | ||
572 | + MultiPointToSinglePointIntent pushedIntent = | ||
573 | + pushedRouteIntents.get(prefix); | ||
574 | + | ||
575 | + // Just for testing. | ||
576 | + if (pushedIntent != null) { | ||
577 | + log.error("There should not be a pushed intent: {}", pushedIntent); | ||
578 | + } | ||
579 | + | ||
580 | + ConnectPoint egressPort = egressInterface.connectPoint(); | ||
581 | + | ||
582 | + Set<ConnectPoint> ingressPorts = new HashSet<>(); | ||
583 | + | ||
584 | + for (Interface intf : interfaceService.getInterfaces()) { | ||
585 | + if (!intf.equals(egressInterface)) { | ||
586 | + ConnectPoint srcPort = intf.connectPoint(); | ||
587 | + ingressPorts.add(srcPort); | ||
588 | + } | ||
589 | + } | ||
590 | + | ||
591 | + // Match the destination IP prefix at the first hop | ||
592 | + //PacketMatchBuilder builder = new PacketMatchBuilder(); | ||
593 | + //builder.setEtherType(Ethernet.TYPE_IPV4).setDstIpNet(prefix); | ||
594 | + //PacketMatch packetMatch = builder.build(); | ||
595 | + TrafficSelector selector = DefaultTrafficSelector.builder() | ||
596 | + .matchEthType(Ethernet.TYPE_IPV4) | ||
597 | + .matchIPDst(prefix) | ||
598 | + .build(); | ||
599 | + | ||
600 | + // Rewrite the destination MAC address | ||
601 | + //ModifyDstMacAction modifyDstMacAction = | ||
602 | + //new ModifyDstMacAction(nextHopMacAddress); | ||
603 | + TrafficTreatment treatment = DefaultTrafficTreatment.builder() | ||
604 | + .setEthDst(nextHopMacAddress) | ||
605 | + .build(); | ||
606 | + | ||
607 | + MultiPointToSinglePointIntent intent = | ||
608 | + new MultiPointToSinglePointIntent(nextIntentId(), | ||
609 | + selector, treatment, ingressPorts, egressPort); | ||
610 | + | ||
611 | + if (isElectedLeader && isActivatedLeader) { | ||
612 | + log.debug("Intent installation: adding Intent for prefix: {}", | ||
613 | + prefix); | ||
614 | + intentService.submit(intent); | ||
615 | + } | ||
616 | + | ||
617 | + // Maintain the Intent | ||
618 | + pushedRouteIntents.put(prefix, intent); | ||
619 | + } | ||
620 | + | ||
621 | + /** | ||
622 | + * Executes deleting a route entry. | ||
623 | + * <p/> | ||
624 | + * Removes prefix from radix tree, and if successful, then try to delete | ||
625 | + * the related intent. | ||
626 | + * | ||
627 | + * @param routeEntry the route entry to delete | ||
628 | + */ | ||
629 | + protected void processRouteDelete(RouteEntry routeEntry) { | ||
630 | + synchronized (this) { | ||
631 | + log.debug("Processing route delete: {}", routeEntry); | ||
632 | + IpPrefix prefix = routeEntry.prefix(); | ||
633 | + | ||
634 | + // TODO check the change of logic here - remove doesn't check that | ||
635 | + // the route entry was what we expected (and we can't do this | ||
636 | + // concurrently) | ||
637 | + | ||
638 | + if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) { | ||
639 | + // | ||
640 | + // Only delete flows if an entry was actually removed from the | ||
641 | + // tree. If no entry was removed, the <prefix, nexthop> wasn't | ||
642 | + // there so it's probably already been removed and we don't | ||
643 | + // need to do anything. | ||
644 | + // | ||
645 | + executeRouteDelete(routeEntry); | ||
646 | + } | ||
647 | + | ||
648 | + routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry); | ||
649 | + // TODO cancel the request in the ARP manager as well | ||
650 | + } | ||
651 | + } | ||
652 | + | ||
653 | + /** | ||
654 | + * Executed deleting a route entry. | ||
655 | + * | ||
656 | + * @param routeEntry the route entry to delete | ||
657 | + */ | ||
658 | + private void executeRouteDelete(RouteEntry routeEntry) { | ||
659 | + log.debug("Executing route delete: {}", routeEntry); | ||
660 | + | ||
661 | + IpPrefix prefix = routeEntry.prefix(); | ||
662 | + | ||
663 | + MultiPointToSinglePointIntent intent = | ||
664 | + pushedRouteIntents.remove(prefix); | ||
665 | + | ||
666 | + if (intent == null) { | ||
667 | + log.debug("There is no intent in pushedRouteIntents to delete " + | ||
668 | + "for prefix: {}", prefix); | ||
669 | + } else { | ||
670 | + if (isElectedLeader && isActivatedLeader) { | ||
671 | + log.debug("Intent installation: deleting Intent for prefix: {}", | ||
672 | + prefix); | ||
673 | + intentService.withdraw(intent); | ||
674 | + } | ||
675 | + } | ||
676 | + } | ||
677 | + | ||
678 | + /** | ||
679 | + * This method handles the prefixes which are waiting for ARP replies for | ||
680 | + * MAC addresses of next hops. | ||
681 | + * | ||
682 | + * @param ipAddress next hop router IP address, for which we sent ARP | ||
683 | + * request out | ||
684 | + * @param macAddress MAC address which is relative to the ipAddress | ||
685 | + */ | ||
686 | + //@Override | ||
687 | + // TODO change name | ||
688 | + public void arpResponse(IpAddress ipAddress, MacAddress macAddress) { | ||
689 | + log.debug("Received ARP response: {} => {}", ipAddress, macAddress); | ||
690 | + | ||
691 | + // We synchronize on this to prevent changes to the radix tree | ||
692 | + // while we're pushing intents. If the tree changes, the | ||
693 | + // tree and intents could get out of sync. | ||
694 | + synchronized (this) { | ||
695 | + | ||
696 | + Set<RouteEntry> routesToPush = | ||
697 | + routesWaitingOnArp.removeAll(ipAddress); | ||
698 | + | ||
699 | + for (RouteEntry routeEntry : routesToPush) { | ||
700 | + // These will always be adds | ||
701 | + IpPrefix prefix = routeEntry.prefix(); | ||
702 | + String binaryString = RouteEntry.createBinaryString(prefix); | ||
703 | + RouteEntry foundRouteEntry = | ||
704 | + bgpRoutes.getValueForExactKey(binaryString); | ||
705 | + if (foundRouteEntry != null && | ||
706 | + foundRouteEntry.nextHop().equals(routeEntry.nextHop())) { | ||
707 | + log.debug("Pushing prefix {} next hop {}", | ||
708 | + routeEntry.prefix(), routeEntry.nextHop()); | ||
709 | + // We only push prefix flows if the prefix is still in the | ||
710 | + // radix tree and the next hop is the same as our | ||
711 | + // update. | ||
712 | + // The prefix could have been removed while we were waiting | ||
713 | + // for the ARP, or the next hop could have changed. | ||
714 | + addRouteIntentToNextHop(prefix, ipAddress, macAddress); | ||
715 | + } else { | ||
716 | + log.debug("Received ARP response, but {}/{} is no longer in" | ||
717 | + + " the radix tree", routeEntry.prefix(), | ||
718 | + routeEntry.nextHop()); | ||
719 | + } | ||
720 | + } | ||
721 | + } | ||
722 | + } | ||
723 | + | ||
724 | + /** | ||
725 | + * Gets the SDN-IP routes. | ||
726 | + * | ||
727 | + * @return the SDN-IP routes | ||
728 | + */ | ||
729 | + public Collection<RouteEntry> getRoutes() { | ||
730 | + Iterator<KeyValuePair<RouteEntry>> it = | ||
731 | + bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator(); | ||
732 | + | ||
733 | + List<RouteEntry> routes = new LinkedList<>(); | ||
734 | + | ||
735 | + while (it.hasNext()) { | ||
736 | + KeyValuePair<RouteEntry> entry = it.next(); | ||
737 | + routes.add(entry.getValue()); | ||
738 | + } | ||
739 | + | ||
740 | + return routes; | ||
741 | + } | ||
742 | + | ||
743 | + /** | ||
744 | + * Generates a new unique intent ID. | ||
745 | + * | ||
746 | + * @return the new intent ID. | ||
747 | + */ | ||
748 | + private IntentId nextIntentId() { | ||
749 | + return new IntentId(intentId++); | ||
750 | + } | ||
751 | + | ||
752 | + /** | ||
753 | + * Listener for host events. | ||
754 | + */ | ||
755 | + class InternalHostListener implements HostListener { | ||
756 | + @Override | ||
757 | + public void event(HostEvent event) { | ||
758 | + if (event.type() == HostEvent.Type.HOST_ADDED || | ||
759 | + event.type() == HostEvent.Type.HOST_UPDATED) { | ||
760 | + Host host = event.subject(); | ||
761 | + for (IpPrefix ip : host.ipAddresses()) { | ||
762 | + arpResponse(ip.toIpAddress(), host.mac()); | ||
763 | + } | ||
764 | + } | ||
765 | + } | ||
766 | + } | ||
767 | +} |
... | @@ -2,21 +2,30 @@ package org.onlab.onos.sdnip; | ... | @@ -2,21 +2,30 @@ package org.onlab.onos.sdnip; |
2 | 2 | ||
3 | import static org.slf4j.LoggerFactory.getLogger; | 3 | import static org.slf4j.LoggerFactory.getLogger; |
4 | 4 | ||
5 | +import java.util.Collection; | ||
6 | + | ||
5 | import org.apache.felix.scr.annotations.Activate; | 7 | import org.apache.felix.scr.annotations.Activate; |
6 | import org.apache.felix.scr.annotations.Component; | 8 | import org.apache.felix.scr.annotations.Component; |
7 | import org.apache.felix.scr.annotations.Deactivate; | 9 | import org.apache.felix.scr.annotations.Deactivate; |
8 | import org.apache.felix.scr.annotations.Reference; | 10 | import org.apache.felix.scr.annotations.Reference; |
9 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 11 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
12 | +import org.apache.felix.scr.annotations.Service; | ||
10 | import org.onlab.onos.net.host.HostService; | 13 | import org.onlab.onos.net.host.HostService; |
11 | import org.onlab.onos.net.intent.IntentService; | 14 | import org.onlab.onos.net.intent.IntentService; |
15 | +import org.onlab.onos.sdnip.RouteUpdate.Type; | ||
16 | +import org.onlab.onos.sdnip.bgp.BgpRouteEntry; | ||
17 | +import org.onlab.onos.sdnip.bgp.BgpSessionManager; | ||
12 | import org.onlab.onos.sdnip.config.SdnIpConfigReader; | 18 | import org.onlab.onos.sdnip.config.SdnIpConfigReader; |
19 | +import org.onlab.packet.IpAddress; | ||
20 | +import org.onlab.packet.IpPrefix; | ||
13 | import org.slf4j.Logger; | 21 | import org.slf4j.Logger; |
14 | 22 | ||
15 | /** | 23 | /** |
16 | - * Placeholder SDN-IP component. | 24 | + * Component for the SDN-IP peering application. |
17 | */ | 25 | */ |
18 | @Component(immediate = true) | 26 | @Component(immediate = true) |
19 | -public class SdnIp { | 27 | +@Service |
28 | +public class SdnIp implements SdnIpService { | ||
20 | 29 | ||
21 | private final Logger log = getLogger(getClass()); | 30 | private final Logger log = getLogger(getClass()); |
22 | 31 | ||
... | @@ -28,6 +37,8 @@ public class SdnIp { | ... | @@ -28,6 +37,8 @@ public class SdnIp { |
28 | 37 | ||
29 | private SdnIpConfigReader config; | 38 | private SdnIpConfigReader config; |
30 | private PeerConnectivity peerConnectivity; | 39 | private PeerConnectivity peerConnectivity; |
40 | + private Router router; | ||
41 | + private BgpSessionManager bgpSessionManager; | ||
31 | 42 | ||
32 | @Activate | 43 | @Activate |
33 | protected void activate() { | 44 | protected void activate() { |
... | @@ -41,10 +52,31 @@ public class SdnIp { | ... | @@ -41,10 +52,31 @@ public class SdnIp { |
41 | peerConnectivity = new PeerConnectivity(config, interfaceService, intentService); | 52 | peerConnectivity = new PeerConnectivity(config, interfaceService, intentService); |
42 | peerConnectivity.start(); | 53 | peerConnectivity.start(); |
43 | 54 | ||
55 | + router = new Router(intentService, hostService, config, interfaceService); | ||
56 | + router.start(); | ||
57 | + | ||
58 | + bgpSessionManager = new BgpSessionManager(router); | ||
59 | + bgpSessionManager.startUp(2000); // TODO | ||
60 | + | ||
61 | + // TODO need to disable link discovery on external ports | ||
62 | + | ||
63 | + router.update(new RouteUpdate(Type.UPDATE, new RouteEntry( | ||
64 | + IpPrefix.valueOf("172.16.20.0/24"), | ||
65 | + IpAddress.valueOf("192.168.10.1")))); | ||
44 | } | 66 | } |
45 | 67 | ||
46 | @Deactivate | 68 | @Deactivate |
47 | protected void deactivate() { | 69 | protected void deactivate() { |
48 | log.info("Stopped"); | 70 | log.info("Stopped"); |
49 | } | 71 | } |
72 | + | ||
73 | + @Override | ||
74 | + public Collection<BgpRouteEntry> getBgpRoutes() { | ||
75 | + return bgpSessionManager.getBgpRoutes(); | ||
76 | + } | ||
77 | + | ||
78 | + @Override | ||
79 | + public Collection<RouteEntry> getRoutes() { | ||
80 | + return router.getRoutes(); | ||
81 | + } | ||
50 | } | 82 | } | ... | ... |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import java.util.Collection; | ||
4 | + | ||
5 | +import org.onlab.onos.sdnip.bgp.BgpRouteEntry; | ||
6 | + | ||
7 | +/** | ||
8 | + * Service interface exported by SDN-IP. | ||
9 | + */ | ||
10 | +public interface SdnIpService { | ||
11 | + /** | ||
12 | + * Gets the BGP routes. | ||
13 | + * | ||
14 | + * @return the BGP routes | ||
15 | + */ | ||
16 | + public Collection<BgpRouteEntry> getBgpRoutes(); | ||
17 | + | ||
18 | + /** | ||
19 | + * Gets all the routes known to SDN-IP. | ||
20 | + * | ||
21 | + * @return the SDN-IP routes | ||
22 | + */ | ||
23 | + public Collection<RouteEntry> getRoutes(); | ||
24 | +} |
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.cli; | ||
2 | + | ||
3 | +import org.apache.karaf.shell.commands.Command; | ||
4 | +import org.onlab.onos.cli.AbstractShellCommand; | ||
5 | +import org.onlab.onos.sdnip.SdnIpService; | ||
6 | +import org.onlab.onos.sdnip.bgp.BgpConstants; | ||
7 | +import org.onlab.onos.sdnip.bgp.BgpRouteEntry; | ||
8 | + | ||
9 | +/** | ||
10 | + * Command to show the routes learned through BGP. | ||
11 | + */ | ||
12 | +@Command(scope = "onos", name = "bgp-routes", | ||
13 | + description = "Lists all routes received from BGP") | ||
14 | +public class BgpRoutesListCommand extends AbstractShellCommand { | ||
15 | + | ||
16 | + private static final String FORMAT = | ||
17 | + "prefix=%s, nexthop=%s, origin=%s, localpref=%s, med=%s, aspath=%s, bgpid=%s"; | ||
18 | + | ||
19 | + @Override | ||
20 | + protected void execute() { | ||
21 | + SdnIpService service = get(SdnIpService.class); | ||
22 | + | ||
23 | + for (BgpRouteEntry route : service.getBgpRoutes()) { | ||
24 | + printRoute(route); | ||
25 | + } | ||
26 | + } | ||
27 | + | ||
28 | + private void printRoute(BgpRouteEntry route) { | ||
29 | + if (route != null) { | ||
30 | + print(FORMAT, route.prefix(), route.nextHop(), | ||
31 | + originToString(route.getOrigin()), route.getLocalPref(), | ||
32 | + route.getMultiExitDisc(), route.getAsPath(), | ||
33 | + route.getBgpSession().getRemoteBgpId()); | ||
34 | + } | ||
35 | + } | ||
36 | + | ||
37 | + private static String originToString(int origin) { | ||
38 | + String originString = "UNKNOWN"; | ||
39 | + | ||
40 | + switch (origin) { | ||
41 | + case BgpConstants.Update.Origin.IGP: | ||
42 | + originString = "IGP"; | ||
43 | + break; | ||
44 | + case BgpConstants.Update.Origin.EGP: | ||
45 | + originString = "EGP"; | ||
46 | + break; | ||
47 | + case BgpConstants.Update.Origin.INCOMPLETE: | ||
48 | + originString = "INCOMPLETE"; | ||
49 | + break; | ||
50 | + default: | ||
51 | + break; | ||
52 | + } | ||
53 | + | ||
54 | + return originString; | ||
55 | + } | ||
56 | + | ||
57 | +} |
1 | +package org.onlab.onos.sdnip.cli; | ||
2 | + | ||
3 | +import org.apache.karaf.shell.commands.Command; | ||
4 | +import org.onlab.onos.cli.AbstractShellCommand; | ||
5 | +import org.onlab.onos.sdnip.RouteEntry; | ||
6 | +import org.onlab.onos.sdnip.SdnIpService; | ||
7 | + | ||
8 | +/** | ||
9 | + * Command to show the list of routes in SDN-IP's routing table. | ||
10 | + */ | ||
11 | +@Command(scope = "onos", name = "routes", | ||
12 | + description = "Lists all routes known to SDN-IP") | ||
13 | +public class RoutesListCommand extends AbstractShellCommand { | ||
14 | + | ||
15 | + private static final String FORMAT = | ||
16 | + "prefix=%s, nexthop=%s"; | ||
17 | + | ||
18 | + @Override | ||
19 | + protected void execute() { | ||
20 | + SdnIpService service = get(SdnIpService.class); | ||
21 | + | ||
22 | + for (RouteEntry route : service.getRoutes()) { | ||
23 | + printRoute(route); | ||
24 | + } | ||
25 | + } | ||
26 | + | ||
27 | + private void printRoute(RouteEntry route) { | ||
28 | + if (route != null) { | ||
29 | + print(FORMAT, route.prefix(), route.nextHop()); | ||
30 | + } | ||
31 | + } | ||
32 | +} |
1 | +<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> | ||
2 | + | ||
3 | + <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0"> | ||
4 | + <command> | ||
5 | + <action class="org.onlab.onos.sdnip.cli.BgpRoutesListCommand"/> | ||
6 | + </command> | ||
7 | + <command> | ||
8 | + <action class="org.onlab.onos.sdnip.cli.RoutesListCommand"/> | ||
9 | + </command> | ||
10 | + </command-bundle> | ||
11 | +</blueprint> |
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 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
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 java.util.ArrayList; | ||
8 | + | ||
9 | +import org.junit.Test; | ||
10 | + | ||
11 | +/** | ||
12 | + * Unit tests for the BgpRouteEntry.AsPath class. | ||
13 | + */ | ||
14 | +public class AsPathTest { | ||
15 | + /** | ||
16 | + * Generates an AS Path. | ||
17 | + * | ||
18 | + * @return a generated AS Path | ||
19 | + */ | ||
20 | + private BgpRouteEntry.AsPath generateAsPath() { | ||
21 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
22 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
23 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
24 | + segmentAsNumbers1.add((long) 1); | ||
25 | + segmentAsNumbers1.add((long) 2); | ||
26 | + segmentAsNumbers1.add((long) 3); | ||
27 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
28 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
29 | + pathSegments.add(pathSegment1); | ||
30 | + // | ||
31 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
32 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
33 | + segmentAsNumbers2.add((long) 4); | ||
34 | + segmentAsNumbers2.add((long) 5); | ||
35 | + segmentAsNumbers2.add((long) 6); | ||
36 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
37 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
38 | + pathSegments.add(pathSegment2); | ||
39 | + // | ||
40 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
41 | + | ||
42 | + return asPath; | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Tests valid class constructor. | ||
47 | + */ | ||
48 | + @Test | ||
49 | + public void testConstructor() { | ||
50 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
51 | + | ||
52 | + String expectedString = | ||
53 | + "AsPath{pathSegments=" + | ||
54 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
55 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}"; | ||
56 | + assertThat(asPath.toString(), is(expectedString)); | ||
57 | + } | ||
58 | + | ||
59 | + /** | ||
60 | + * Tests invalid class constructor for null Path Segments. | ||
61 | + */ | ||
62 | + @Test(expected = NullPointerException.class) | ||
63 | + public void testInvalidConstructorNullPathSegments() { | ||
64 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = null; | ||
65 | + new BgpRouteEntry.AsPath(pathSegments); | ||
66 | + } | ||
67 | + | ||
68 | + /** | ||
69 | + * Tests getting the fields of an AS Path. | ||
70 | + */ | ||
71 | + @Test | ||
72 | + public void testGetFields() { | ||
73 | + // Create the fields to compare against | ||
74 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
75 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
76 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
77 | + segmentAsNumbers1.add((long) 1); | ||
78 | + segmentAsNumbers1.add((long) 2); | ||
79 | + segmentAsNumbers1.add((long) 3); | ||
80 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
81 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
82 | + pathSegments.add(pathSegment1); | ||
83 | + // | ||
84 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
85 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
86 | + segmentAsNumbers2.add((long) 4); | ||
87 | + segmentAsNumbers2.add((long) 5); | ||
88 | + segmentAsNumbers2.add((long) 6); | ||
89 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
90 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
91 | + pathSegments.add(pathSegment2); | ||
92 | + | ||
93 | + // Generate the entry to test | ||
94 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
95 | + | ||
96 | + assertThat(asPath.getPathSegments(), is(pathSegments)); | ||
97 | + } | ||
98 | + | ||
99 | + /** | ||
100 | + * Tests getting the AS Path Length. | ||
101 | + */ | ||
102 | + @Test | ||
103 | + public void testGetAsPathLength() { | ||
104 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
105 | + assertThat(asPath.getAsPathLength(), is(4)); | ||
106 | + | ||
107 | + // Create an empty AS Path | ||
108 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
109 | + asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
110 | + assertThat(asPath.getAsPathLength(), is(0)); | ||
111 | + } | ||
112 | + | ||
113 | + /** | ||
114 | + * Tests equality of {@link BgpRouteEntry.AsPath}. | ||
115 | + */ | ||
116 | + @Test | ||
117 | + public void testEquality() { | ||
118 | + BgpRouteEntry.AsPath asPath1 = generateAsPath(); | ||
119 | + BgpRouteEntry.AsPath asPath2 = generateAsPath(); | ||
120 | + | ||
121 | + assertThat(asPath1, is(asPath2)); | ||
122 | + } | ||
123 | + | ||
124 | + /** | ||
125 | + * Tests non-equality of {@link BgpRouteEntry.AsPath}. | ||
126 | + */ | ||
127 | + @Test | ||
128 | + public void testNonEquality() { | ||
129 | + BgpRouteEntry.AsPath asPath1 = generateAsPath(); | ||
130 | + | ||
131 | + // Setup AS Path 2 | ||
132 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
133 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
134 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
135 | + segmentAsNumbers1.add((long) 1); | ||
136 | + segmentAsNumbers1.add((long) 2); | ||
137 | + segmentAsNumbers1.add((long) 3); | ||
138 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
139 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
140 | + pathSegments.add(pathSegment1); | ||
141 | + // | ||
142 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
143 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
144 | + segmentAsNumbers2.add((long) 4); | ||
145 | + segmentAsNumbers2.add((long) 55); // Different | ||
146 | + segmentAsNumbers2.add((long) 6); | ||
147 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
148 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
149 | + pathSegments.add(pathSegment2); | ||
150 | + // | ||
151 | + BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments); | ||
152 | + | ||
153 | + assertThat(asPath1, is(not(asPath2))); | ||
154 | + } | ||
155 | + | ||
156 | + /** | ||
157 | + * Tests object string representation. | ||
158 | + */ | ||
159 | + @Test | ||
160 | + public void testToString() { | ||
161 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
162 | + | ||
163 | + String expectedString = | ||
164 | + "AsPath{pathSegments=" + | ||
165 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
166 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}"; | ||
167 | + assertThat(asPath.toString(), is(expectedString)); | ||
168 | + } | ||
169 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import static org.easymock.EasyMock.createMock; | ||
4 | +import static org.easymock.EasyMock.expect; | ||
5 | +import static org.easymock.EasyMock.replay; | ||
6 | +import static org.hamcrest.Matchers.is; | ||
7 | +import static org.hamcrest.Matchers.not; | ||
8 | +import static org.junit.Assert.assertThat; | ||
9 | + | ||
10 | +import java.util.ArrayList; | ||
11 | + | ||
12 | +import org.junit.Before; | ||
13 | +import org.junit.Test; | ||
14 | +import org.onlab.packet.IpAddress; | ||
15 | +import org.onlab.packet.IpPrefix; | ||
16 | + | ||
17 | +/** | ||
18 | + * Unit tests for the BgpRouteEntry class. | ||
19 | + */ | ||
20 | +public class BgpRouteEntryTest { | ||
21 | + private BgpSession bgpSession; | ||
22 | + private static final IpAddress BGP_SESSION_BGP_ID = | ||
23 | + IpAddress.valueOf("10.0.0.1"); | ||
24 | + private static final IpAddress BGP_SESSION_IP_ADDRESS = | ||
25 | + IpAddress.valueOf("20.0.0.1"); | ||
26 | + | ||
27 | + private BgpSession bgpSession2; | ||
28 | + private static final IpAddress BGP_SESSION_BGP_ID2 = | ||
29 | + IpAddress.valueOf("10.0.0.2"); | ||
30 | + private static final IpAddress BGP_SESSION_IP_ADDRESS2 = | ||
31 | + IpAddress.valueOf("20.0.0.1"); | ||
32 | + | ||
33 | + private BgpSession bgpSession3; | ||
34 | + private static final IpAddress BGP_SESSION_BGP_ID3 = | ||
35 | + IpAddress.valueOf("10.0.0.1"); | ||
36 | + private static final IpAddress BGP_SESSION_IP_ADDRESS3 = | ||
37 | + IpAddress.valueOf("20.0.0.2"); | ||
38 | + | ||
39 | + @Before | ||
40 | + public void setUp() throws Exception { | ||
41 | + // Mock objects for testing | ||
42 | + bgpSession = createMock(BgpSession.class); | ||
43 | + bgpSession2 = createMock(BgpSession.class); | ||
44 | + bgpSession3 = createMock(BgpSession.class); | ||
45 | + | ||
46 | + // Setup the BGP Sessions | ||
47 | + expect(bgpSession.getRemoteBgpId()) | ||
48 | + .andReturn(BGP_SESSION_BGP_ID).anyTimes(); | ||
49 | + expect(bgpSession.getRemoteIp4Address()) | ||
50 | + .andReturn(BGP_SESSION_IP_ADDRESS).anyTimes(); | ||
51 | + // | ||
52 | + expect(bgpSession2.getRemoteBgpId()) | ||
53 | + .andReturn(BGP_SESSION_BGP_ID2).anyTimes(); | ||
54 | + expect(bgpSession2.getRemoteIp4Address()) | ||
55 | + .andReturn(BGP_SESSION_IP_ADDRESS2).anyTimes(); | ||
56 | + // | ||
57 | + expect(bgpSession3.getRemoteBgpId()) | ||
58 | + .andReturn(BGP_SESSION_BGP_ID3).anyTimes(); | ||
59 | + expect(bgpSession3.getRemoteIp4Address()) | ||
60 | + .andReturn(BGP_SESSION_IP_ADDRESS3).anyTimes(); | ||
61 | + | ||
62 | + replay(bgpSession); | ||
63 | + replay(bgpSession2); | ||
64 | + replay(bgpSession3); | ||
65 | + } | ||
66 | + | ||
67 | + /** | ||
68 | + * Generates a BGP Route Entry. | ||
69 | + * | ||
70 | + * @return a generated BGP Route Entry | ||
71 | + */ | ||
72 | + private BgpRouteEntry generateBgpRouteEntry() { | ||
73 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
74 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
75 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
76 | + // Setup the AS Path | ||
77 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
78 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
79 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
80 | + segmentAsNumbers1.add((long) 1); | ||
81 | + segmentAsNumbers1.add((long) 2); | ||
82 | + segmentAsNumbers1.add((long) 3); | ||
83 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
84 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
85 | + pathSegments.add(pathSegment1); | ||
86 | + // | ||
87 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
88 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
89 | + segmentAsNumbers2.add((long) 4); | ||
90 | + segmentAsNumbers2.add((long) 5); | ||
91 | + segmentAsNumbers2.add((long) 6); | ||
92 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
93 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
94 | + pathSegments.add(pathSegment2); | ||
95 | + // | ||
96 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
97 | + // | ||
98 | + long localPref = 100; | ||
99 | + long multiExitDisc = 20; | ||
100 | + | ||
101 | + BgpRouteEntry bgpRouteEntry = | ||
102 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
103 | + localPref); | ||
104 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
105 | + | ||
106 | + return bgpRouteEntry; | ||
107 | + } | ||
108 | + | ||
109 | + /** | ||
110 | + * Tests valid class constructor. | ||
111 | + */ | ||
112 | + @Test | ||
113 | + public void testConstructor() { | ||
114 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
115 | + | ||
116 | + String expectedString = | ||
117 | + "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " + | ||
118 | + "bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" + | ||
119 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
120 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " + | ||
121 | + "localPref=100, multiExitDisc=20}"; | ||
122 | + assertThat(bgpRouteEntry.toString(), is(expectedString)); | ||
123 | + } | ||
124 | + | ||
125 | + /** | ||
126 | + * Tests invalid class constructor for null BGP Session. | ||
127 | + */ | ||
128 | + @Test(expected = NullPointerException.class) | ||
129 | + public void testInvalidConstructorNullBgpSession() { | ||
130 | + BgpSession bgpSessionNull = null; | ||
131 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
132 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
133 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
134 | + // Setup the AS Path | ||
135 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
136 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
137 | + // | ||
138 | + long localPref = 100; | ||
139 | + | ||
140 | + new BgpRouteEntry(bgpSessionNull, prefix, nextHop, origin, asPath, | ||
141 | + localPref); | ||
142 | + } | ||
143 | + | ||
144 | + /** | ||
145 | + * Tests invalid class constructor for null AS Path. | ||
146 | + */ | ||
147 | + @Test(expected = NullPointerException.class) | ||
148 | + public void testInvalidConstructorNullAsPath() { | ||
149 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
150 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
151 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
152 | + BgpRouteEntry.AsPath asPath = null; | ||
153 | + long localPref = 100; | ||
154 | + | ||
155 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
156 | + localPref); | ||
157 | + } | ||
158 | + | ||
159 | + /** | ||
160 | + * Tests getting the fields of a BGP route entry. | ||
161 | + */ | ||
162 | + @Test | ||
163 | + public void testGetFields() { | ||
164 | + // Create the fields to compare against | ||
165 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
166 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
167 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
168 | + // Setup the AS Path | ||
169 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
170 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
171 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
172 | + segmentAsNumbers1.add((long) 1); | ||
173 | + segmentAsNumbers1.add((long) 2); | ||
174 | + segmentAsNumbers1.add((long) 3); | ||
175 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
176 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
177 | + pathSegments.add(pathSegment1); | ||
178 | + // | ||
179 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
180 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
181 | + segmentAsNumbers2.add((long) 4); | ||
182 | + segmentAsNumbers2.add((long) 5); | ||
183 | + segmentAsNumbers2.add((long) 6); | ||
184 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
185 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
186 | + pathSegments.add(pathSegment2); | ||
187 | + // | ||
188 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
189 | + // | ||
190 | + long localPref = 100; | ||
191 | + long multiExitDisc = 20; | ||
192 | + | ||
193 | + // Generate the entry to test | ||
194 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
195 | + | ||
196 | + assertThat(bgpRouteEntry.prefix(), is(prefix)); | ||
197 | + assertThat(bgpRouteEntry.nextHop(), is(nextHop)); | ||
198 | + assertThat(bgpRouteEntry.getBgpSession(), is(bgpSession)); | ||
199 | + assertThat(bgpRouteEntry.getOrigin(), is(origin)); | ||
200 | + assertThat(bgpRouteEntry.getAsPath(), is(asPath)); | ||
201 | + assertThat(bgpRouteEntry.getLocalPref(), is(localPref)); | ||
202 | + assertThat(bgpRouteEntry.getMultiExitDisc(), is(multiExitDisc)); | ||
203 | + } | ||
204 | + | ||
205 | + /** | ||
206 | + * Tests whether a BGP route entry is a local route. | ||
207 | + */ | ||
208 | + @Test | ||
209 | + public void testIsLocalRoute() { | ||
210 | + // | ||
211 | + // Test non-local route | ||
212 | + // | ||
213 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
214 | + assertThat(bgpRouteEntry.isLocalRoute(), is(false)); | ||
215 | + | ||
216 | + // | ||
217 | + // Test local route with AS Path that begins with AS_SET | ||
218 | + // | ||
219 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
220 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
221 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
222 | + // Setup the AS Path | ||
223 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
224 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
225 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
226 | + segmentAsNumbers1.add((long) 1); | ||
227 | + segmentAsNumbers1.add((long) 2); | ||
228 | + segmentAsNumbers1.add((long) 3); | ||
229 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
230 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
231 | + pathSegments.add(pathSegment1); | ||
232 | + // | ||
233 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
234 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
235 | + segmentAsNumbers2.add((long) 4); | ||
236 | + segmentAsNumbers2.add((long) 5); | ||
237 | + segmentAsNumbers2.add((long) 6); | ||
238 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
239 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
240 | + pathSegments.add(pathSegment2); | ||
241 | + // | ||
242 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
243 | + // | ||
244 | + long localPref = 100; | ||
245 | + long multiExitDisc = 20; | ||
246 | + // | ||
247 | + bgpRouteEntry = | ||
248 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
249 | + localPref); | ||
250 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
251 | + assertThat(bgpRouteEntry.isLocalRoute(), is(true)); | ||
252 | + | ||
253 | + // | ||
254 | + // Test local route with empty AS Path | ||
255 | + // | ||
256 | + pathSegments = new ArrayList<>(); | ||
257 | + asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
258 | + bgpRouteEntry = | ||
259 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
260 | + localPref); | ||
261 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
262 | + assertThat(bgpRouteEntry.isLocalRoute(), is(true)); | ||
263 | + } | ||
264 | + | ||
265 | + /** | ||
266 | + * Tests getting the BGP Neighbor AS number for a route. | ||
267 | + */ | ||
268 | + @Test | ||
269 | + public void testGetNeighborAs() { | ||
270 | + // | ||
271 | + // Get neighbor AS for non-local route | ||
272 | + // | ||
273 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
274 | + assertThat(bgpRouteEntry.getNeighborAs(), is((long) 1)); | ||
275 | + | ||
276 | + // | ||
277 | + // Get neighbor AS for a local route | ||
278 | + // | ||
279 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
280 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
281 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
282 | + // Setup the AS Path | ||
283 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
284 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
285 | + // | ||
286 | + long localPref = 100; | ||
287 | + long multiExitDisc = 20; | ||
288 | + // | ||
289 | + bgpRouteEntry = | ||
290 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
291 | + localPref); | ||
292 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
293 | + assertThat(bgpRouteEntry.getNeighborAs(), is(BgpConstants.BGP_AS_0)); | ||
294 | + } | ||
295 | + | ||
296 | + /** | ||
297 | + * Tests whether a BGP route entry has AS Path loop. | ||
298 | + */ | ||
299 | + @Test | ||
300 | + public void testHasAsPathLoop() { | ||
301 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
302 | + | ||
303 | + // Test for loops: test each AS number in the interval [1, 6] | ||
304 | + for (int i = 1; i <= 6; i++) { | ||
305 | + assertThat(bgpRouteEntry.hasAsPathLoop(i), is(true)); | ||
306 | + } | ||
307 | + | ||
308 | + // Test for non-loops | ||
309 | + assertThat(bgpRouteEntry.hasAsPathLoop(500), is(false)); | ||
310 | + } | ||
311 | + | ||
312 | + /** | ||
313 | + * Tests the BGP Decision Process comparison of BGP routes. | ||
314 | + */ | ||
315 | + @Test | ||
316 | + public void testBgpDecisionProcessComparison() { | ||
317 | + BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry(); | ||
318 | + BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry(); | ||
319 | + | ||
320 | + // | ||
321 | + // Compare two routes that are same | ||
322 | + // | ||
323 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
324 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true)); | ||
325 | + | ||
326 | + // | ||
327 | + // Compare two routes with different LOCAL_PREF | ||
328 | + // | ||
329 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
330 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
331 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
332 | + // Setup the AS Path | ||
333 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
334 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
335 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
336 | + segmentAsNumbers1.add((long) 1); | ||
337 | + segmentAsNumbers1.add((long) 2); | ||
338 | + segmentAsNumbers1.add((long) 3); | ||
339 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
340 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
341 | + pathSegments.add(pathSegment1); | ||
342 | + // | ||
343 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
344 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
345 | + segmentAsNumbers2.add((long) 4); | ||
346 | + segmentAsNumbers2.add((long) 5); | ||
347 | + segmentAsNumbers2.add((long) 6); | ||
348 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
349 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
350 | + pathSegments.add(pathSegment2); | ||
351 | + // | ||
352 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
353 | + // | ||
354 | + long localPref = 50; // Different | ||
355 | + long multiExitDisc = 20; | ||
356 | + bgpRouteEntry2 = | ||
357 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
358 | + localPref); | ||
359 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
360 | + // | ||
361 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
362 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
363 | + localPref = bgpRouteEntry1.getLocalPref(); // Restore | ||
364 | + | ||
365 | + // | ||
366 | + // Compare two routes with different AS_PATH length | ||
367 | + // | ||
368 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments2 = new ArrayList<>(); | ||
369 | + pathSegments2.add(pathSegment1); | ||
370 | + // Different AS Path | ||
371 | + BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments2); | ||
372 | + bgpRouteEntry2 = | ||
373 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath2, | ||
374 | + localPref); | ||
375 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
376 | + // | ||
377 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(false)); | ||
378 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true)); | ||
379 | + | ||
380 | + // | ||
381 | + // Compare two routes with different ORIGIN | ||
382 | + // | ||
383 | + origin = BgpConstants.Update.Origin.EGP; // Different | ||
384 | + bgpRouteEntry2 = | ||
385 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
386 | + localPref); | ||
387 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
388 | + // | ||
389 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
390 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
391 | + origin = bgpRouteEntry1.getOrigin(); // Restore | ||
392 | + | ||
393 | + // | ||
394 | + // Compare two routes with different MULTI_EXIT_DISC | ||
395 | + // | ||
396 | + multiExitDisc = 10; // Different | ||
397 | + bgpRouteEntry2 = | ||
398 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
399 | + localPref); | ||
400 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
401 | + // | ||
402 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
403 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
404 | + multiExitDisc = bgpRouteEntry1.getMultiExitDisc(); // Restore | ||
405 | + | ||
406 | + // | ||
407 | + // Compare two routes with different BGP ID | ||
408 | + // | ||
409 | + bgpRouteEntry2 = | ||
410 | + new BgpRouteEntry(bgpSession2, prefix, nextHop, origin, asPath, | ||
411 | + localPref); | ||
412 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
413 | + // | ||
414 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
415 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
416 | + | ||
417 | + // | ||
418 | + // Compare two routes with different BGP address | ||
419 | + // | ||
420 | + bgpRouteEntry2 = | ||
421 | + new BgpRouteEntry(bgpSession3, prefix, nextHop, origin, asPath, | ||
422 | + localPref); | ||
423 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
424 | + // | ||
425 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
426 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
427 | + } | ||
428 | + | ||
429 | + /** | ||
430 | + * Tests equality of {@link BgpRouteEntry}. | ||
431 | + */ | ||
432 | + @Test | ||
433 | + public void testEquality() { | ||
434 | + BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry(); | ||
435 | + BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry(); | ||
436 | + | ||
437 | + assertThat(bgpRouteEntry1, is(bgpRouteEntry2)); | ||
438 | + } | ||
439 | + | ||
440 | + /** | ||
441 | + * Tests non-equality of {@link BgpRouteEntry}. | ||
442 | + */ | ||
443 | + @Test | ||
444 | + public void testNonEquality() { | ||
445 | + BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry(); | ||
446 | + | ||
447 | + // Setup BGP Route 2 | ||
448 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
449 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
450 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
451 | + // Setup the AS Path | ||
452 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
453 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
454 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
455 | + segmentAsNumbers1.add((long) 1); | ||
456 | + segmentAsNumbers1.add((long) 2); | ||
457 | + segmentAsNumbers1.add((long) 3); | ||
458 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
459 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
460 | + pathSegments.add(pathSegment1); | ||
461 | + // | ||
462 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
463 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
464 | + segmentAsNumbers2.add((long) 4); | ||
465 | + segmentAsNumbers2.add((long) 5); | ||
466 | + segmentAsNumbers2.add((long) 6); | ||
467 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
468 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
469 | + pathSegments.add(pathSegment2); | ||
470 | + // | ||
471 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
472 | + // | ||
473 | + long localPref = 500; // Different | ||
474 | + long multiExitDisc = 20; | ||
475 | + BgpRouteEntry bgpRouteEntry2 = | ||
476 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
477 | + localPref); | ||
478 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
479 | + | ||
480 | + assertThat(bgpRouteEntry1, is(not(bgpRouteEntry2))); | ||
481 | + } | ||
482 | + | ||
483 | + /** | ||
484 | + * Tests object string representation. | ||
485 | + */ | ||
486 | + @Test | ||
487 | + public void testToString() { | ||
488 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
489 | + | ||
490 | + String expectedString = | ||
491 | + "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " + | ||
492 | + "bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" + | ||
493 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
494 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " + | ||
495 | + "localPref=100, multiExitDisc=20}"; | ||
496 | + assertThat(bgpRouteEntry.toString(), is(expectedString)); | ||
497 | + } | ||
498 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import static org.hamcrest.Matchers.hasItem; | ||
4 | +import static org.hamcrest.Matchers.hasSize; | ||
5 | +import static org.hamcrest.Matchers.is; | ||
6 | +import static org.hamcrest.Matchers.notNullValue; | ||
7 | +import static org.junit.Assert.assertThat; | ||
8 | + | ||
9 | +import java.net.InetAddress; | ||
10 | +import java.net.InetSocketAddress; | ||
11 | +import java.net.SocketAddress; | ||
12 | +import java.util.ArrayList; | ||
13 | +import java.util.Collection; | ||
14 | +import java.util.LinkedList; | ||
15 | +import java.util.concurrent.Executors; | ||
16 | +import java.util.concurrent.TimeUnit; | ||
17 | + | ||
18 | +import org.jboss.netty.bootstrap.ClientBootstrap; | ||
19 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
20 | +import org.jboss.netty.channel.Channel; | ||
21 | +import org.jboss.netty.channel.ChannelFactory; | ||
22 | +import org.jboss.netty.channel.ChannelPipeline; | ||
23 | +import org.jboss.netty.channel.ChannelPipelineFactory; | ||
24 | +import org.jboss.netty.channel.Channels; | ||
25 | +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; | ||
26 | +import org.junit.After; | ||
27 | +import org.junit.Before; | ||
28 | +import org.junit.Test; | ||
29 | +import org.onlab.onos.sdnip.RouteListener; | ||
30 | +import org.onlab.onos.sdnip.RouteUpdate; | ||
31 | +import org.onlab.packet.IpAddress; | ||
32 | +import org.onlab.packet.IpPrefix; | ||
33 | +import org.onlab.util.TestUtils; | ||
34 | +import org.onlab.util.TestUtils.TestUtilsException; | ||
35 | + | ||
36 | +import com.google.common.net.InetAddresses; | ||
37 | + | ||
38 | +/** | ||
39 | + * Unit tests for the BgpSessionManager class. | ||
40 | + */ | ||
41 | +public class BgpSessionManagerTest { | ||
42 | + private static final IpAddress IP_LOOPBACK_ID = | ||
43 | + IpAddress.valueOf("127.0.0.1"); | ||
44 | + private static final IpAddress BGP_PEER1_ID = IpAddress.valueOf("10.0.0.1"); | ||
45 | + private static final long DEFAULT_LOCAL_PREF = 10; | ||
46 | + private static final long DEFAULT_MULTI_EXIT_DISC = 20; | ||
47 | + | ||
48 | + // The BGP Session Manager to test | ||
49 | + private BgpSessionManager bgpSessionManager; | ||
50 | + | ||
51 | + // Remote Peer state | ||
52 | + private ClientBootstrap peerBootstrap; | ||
53 | + private TestBgpPeerChannelHandler peerChannelHandler = | ||
54 | + new TestBgpPeerChannelHandler(BGP_PEER1_ID, DEFAULT_LOCAL_PREF); | ||
55 | + private TestBgpPeerFrameDecoder peerFrameDecoder = | ||
56 | + new TestBgpPeerFrameDecoder(); | ||
57 | + | ||
58 | + // The socket that the Remote Peer should connect to | ||
59 | + private InetSocketAddress connectToSocket; | ||
60 | + | ||
61 | + private final DummyRouteListener dummyRouteListener = | ||
62 | + new DummyRouteListener(); | ||
63 | + | ||
64 | + /** | ||
65 | + * Dummy implementation for the RouteListener interface. | ||
66 | + */ | ||
67 | + private class DummyRouteListener implements RouteListener { | ||
68 | + @Override | ||
69 | + public void update(RouteUpdate routeUpdate) { | ||
70 | + // Nothing to do | ||
71 | + } | ||
72 | + } | ||
73 | + | ||
74 | + @Before | ||
75 | + public void setUp() throws Exception { | ||
76 | + // | ||
77 | + // Setup the BGP Session Manager to test, and start listening for BGP | ||
78 | + // connections. | ||
79 | + // | ||
80 | + bgpSessionManager = new BgpSessionManager(dummyRouteListener); | ||
81 | + // NOTE: We use port 0 to bind on any available port | ||
82 | + bgpSessionManager.startUp(0); | ||
83 | + | ||
84 | + // Get the port number the BGP Session Manager is listening on | ||
85 | + Channel serverChannel = TestUtils.getField(bgpSessionManager, | ||
86 | + "serverChannel"); | ||
87 | + SocketAddress socketAddress = serverChannel.getLocalAddress(); | ||
88 | + InetSocketAddress inetSocketAddress = | ||
89 | + (InetSocketAddress) socketAddress; | ||
90 | + | ||
91 | + // | ||
92 | + // Setup the BGP Peer, i.e., the "remote" BGP router that will | ||
93 | + // initiate the BGP connection, send BGP UPDATE messages, etc. | ||
94 | + // | ||
95 | + ChannelFactory channelFactory = | ||
96 | + new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), | ||
97 | + Executors.newCachedThreadPool()); | ||
98 | + ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() { | ||
99 | + @Override | ||
100 | + public ChannelPipeline getPipeline() throws Exception { | ||
101 | + // Setup the transmitting pipeline | ||
102 | + ChannelPipeline pipeline = Channels.pipeline(); | ||
103 | + pipeline.addLast("TestBgpPeerFrameDecoder", | ||
104 | + peerFrameDecoder); | ||
105 | + pipeline.addLast("TestBgpPeerChannelHandler", | ||
106 | + peerChannelHandler); | ||
107 | + return pipeline; | ||
108 | + } | ||
109 | + }; | ||
110 | + | ||
111 | + peerBootstrap = new ClientBootstrap(channelFactory); | ||
112 | + peerBootstrap.setOption("child.keepAlive", true); | ||
113 | + peerBootstrap.setOption("child.tcpNoDelay", true); | ||
114 | + peerBootstrap.setPipelineFactory(pipelineFactory); | ||
115 | + | ||
116 | + InetAddress connectToAddress = InetAddresses.forString("127.0.0.1"); | ||
117 | + connectToSocket = new InetSocketAddress(connectToAddress, | ||
118 | + inetSocketAddress.getPort()); | ||
119 | + } | ||
120 | + | ||
121 | + @After | ||
122 | + public void tearDown() throws Exception { | ||
123 | + bgpSessionManager.shutDown(); | ||
124 | + bgpSessionManager = null; | ||
125 | + } | ||
126 | + | ||
127 | + /** | ||
128 | + * Gets BGP RIB-IN routes by waiting until they are received. | ||
129 | + * <p/> | ||
130 | + * NOTE: We keep checking once a second the number of received routes, | ||
131 | + * up to 5 seconds. | ||
132 | + * | ||
133 | + * @param bgpSession the BGP session that is expected to receive the | ||
134 | + * routes | ||
135 | + * @param expectedRoutes the expected number of routes | ||
136 | + * @return the BGP RIB-IN routes as received within the expected | ||
137 | + * time interval | ||
138 | + */ | ||
139 | + private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession, | ||
140 | + long expectedRoutes) | ||
141 | + throws InterruptedException { | ||
142 | + Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn(); | ||
143 | + | ||
144 | + final int maxChecks = 5; // Max wait of 5 seconds | ||
145 | + for (int i = 0; i < maxChecks; i++) { | ||
146 | + if (bgpRibIn.size() == expectedRoutes) { | ||
147 | + break; | ||
148 | + } | ||
149 | + Thread.sleep(1000); | ||
150 | + bgpRibIn = bgpSession.getBgpRibIn(); | ||
151 | + } | ||
152 | + | ||
153 | + return bgpRibIn; | ||
154 | + } | ||
155 | + | ||
156 | + /** | ||
157 | + * Gets BGP merged routes by waiting until they are received. | ||
158 | + * <p/> | ||
159 | + * NOTE: We keep checking once a second the number of received routes, | ||
160 | + * up to 5 seconds. | ||
161 | + * | ||
162 | + * @param expectedRoutes the expected number of routes | ||
163 | + * @return the BGP Session Manager routes as received within the expected | ||
164 | + * time interval | ||
165 | + */ | ||
166 | + private Collection<BgpRouteEntry> waitForBgpRoutes(long expectedRoutes) | ||
167 | + throws InterruptedException { | ||
168 | + Collection<BgpRouteEntry> bgpRoutes = bgpSessionManager.getBgpRoutes(); | ||
169 | + | ||
170 | + final int maxChecks = 5; // Max wait of 5 seconds | ||
171 | + for (int i = 0; i < maxChecks; i++) { | ||
172 | + if (bgpRoutes.size() == expectedRoutes) { | ||
173 | + break; | ||
174 | + } | ||
175 | + Thread.sleep(1000); | ||
176 | + bgpRoutes = bgpSessionManager.getBgpRoutes(); | ||
177 | + } | ||
178 | + | ||
179 | + return bgpRoutes; | ||
180 | + } | ||
181 | + | ||
182 | + /** | ||
183 | + * Tests that the BGP OPEN messages have been exchanged, followed by | ||
184 | + * KEEPALIVE. | ||
185 | + * <p> | ||
186 | + * The BGP Peer opens the sessions and transmits OPEN Message, eventually | ||
187 | + * followed by KEEPALIVE. The tested BGP listener should respond by | ||
188 | + * OPEN Message, followed by KEEPALIVE. | ||
189 | + * | ||
190 | + * @throws TestUtilsException TestUtils error | ||
191 | + */ | ||
192 | + @Test | ||
193 | + public void testExchangedBgpOpenMessages() | ||
194 | + throws InterruptedException, TestUtilsException { | ||
195 | + // Initiate the connection | ||
196 | + peerBootstrap.connect(connectToSocket); | ||
197 | + | ||
198 | + // Wait until the OPEN message is received | ||
199 | + peerFrameDecoder.receivedOpenMessageLatch.await(2000, | ||
200 | + TimeUnit.MILLISECONDS); | ||
201 | + // Wait until the KEEPALIVE message is received | ||
202 | + peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000, | ||
203 | + TimeUnit.MILLISECONDS); | ||
204 | + | ||
205 | + // | ||
206 | + // Test the fields from the BGP OPEN message: | ||
207 | + // BGP version, AS number, BGP ID | ||
208 | + // | ||
209 | + assertThat(peerFrameDecoder.remoteBgpVersion, | ||
210 | + is(BgpConstants.BGP_VERSION)); | ||
211 | + assertThat(peerFrameDecoder.remoteAs, | ||
212 | + is(TestBgpPeerChannelHandler.PEER_AS)); | ||
213 | + assertThat(peerFrameDecoder.remoteBgpIdentifier, is(IP_LOOPBACK_ID)); | ||
214 | + | ||
215 | + // | ||
216 | + // Test that a BgpSession instance has been created | ||
217 | + // | ||
218 | + assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID)); | ||
219 | + assertThat(bgpSessionManager.getBgpSessions(), hasSize(1)); | ||
220 | + BgpSession bgpSession = | ||
221 | + bgpSessionManager.getBgpSessions().iterator().next(); | ||
222 | + assertThat(bgpSession, notNullValue()); | ||
223 | + long sessionAs = TestUtils.getField(bgpSession, "localAs"); | ||
224 | + assertThat(sessionAs, is(TestBgpPeerChannelHandler.PEER_AS)); | ||
225 | + } | ||
226 | + | ||
227 | + /** | ||
228 | + * Tests that the BGP UPDATE messages have been received and processed. | ||
229 | + */ | ||
230 | + @Test | ||
231 | + public void testProcessedBgpUpdateMessages() throws InterruptedException { | ||
232 | + BgpSession bgpSession; | ||
233 | + IpAddress nextHopRouter; | ||
234 | + BgpRouteEntry bgpRouteEntry; | ||
235 | + ChannelBuffer message; | ||
236 | + Collection<BgpRouteEntry> bgpRibIn; | ||
237 | + Collection<BgpRouteEntry> bgpRoutes; | ||
238 | + | ||
239 | + // Initiate the connection | ||
240 | + peerBootstrap.connect(connectToSocket); | ||
241 | + | ||
242 | + // Wait until the OPEN message is received | ||
243 | + peerFrameDecoder.receivedOpenMessageLatch.await(2000, | ||
244 | + TimeUnit.MILLISECONDS); | ||
245 | + // Wait until the KEEPALIVE message is received | ||
246 | + peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000, | ||
247 | + TimeUnit.MILLISECONDS); | ||
248 | + | ||
249 | + // Get the BGP Session handler | ||
250 | + bgpSession = bgpSessionManager.getBgpSessions().iterator().next(); | ||
251 | + | ||
252 | + // Prepare routes to add/delete | ||
253 | + nextHopRouter = IpAddress.valueOf("10.20.30.40"); | ||
254 | + Collection<IpPrefix> addedRoutes = new LinkedList<>(); | ||
255 | + Collection<IpPrefix> withdrawnRoutes = new LinkedList<>(); | ||
256 | + addedRoutes.add(IpPrefix.valueOf("0.0.0.0/0")); | ||
257 | + addedRoutes.add(IpPrefix.valueOf("20.0.0.0/8")); | ||
258 | + addedRoutes.add(IpPrefix.valueOf("30.0.0.0/16")); | ||
259 | + addedRoutes.add(IpPrefix.valueOf("40.0.0.0/24")); | ||
260 | + addedRoutes.add(IpPrefix.valueOf("50.0.0.0/32")); | ||
261 | + withdrawnRoutes.add(IpPrefix.valueOf("60.0.0.0/8")); | ||
262 | + withdrawnRoutes.add(IpPrefix.valueOf("70.0.0.0/16")); | ||
263 | + withdrawnRoutes.add(IpPrefix.valueOf("80.0.0.0/24")); | ||
264 | + withdrawnRoutes.add(IpPrefix.valueOf("90.0.0.0/32")); | ||
265 | + // Write the routes | ||
266 | + message = peerChannelHandler.prepareBgpUpdate(nextHopRouter, | ||
267 | + addedRoutes, | ||
268 | + withdrawnRoutes); | ||
269 | + peerChannelHandler.savedCtx.getChannel().write(message); | ||
270 | + | ||
271 | + // Check that the routes have been received, processed and stored | ||
272 | + bgpRibIn = waitForBgpRibIn(bgpSession, 5); | ||
273 | + assertThat(bgpRibIn, hasSize(5)); | ||
274 | + bgpRoutes = waitForBgpRoutes(5); | ||
275 | + assertThat(bgpRoutes, hasSize(5)); | ||
276 | + | ||
277 | + // Setup the AS Path | ||
278 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
279 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
280 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
281 | + segmentAsNumbers1.add((long) 65010); | ||
282 | + segmentAsNumbers1.add((long) 65020); | ||
283 | + segmentAsNumbers1.add((long) 65030); | ||
284 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
285 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
286 | + pathSegments.add(pathSegment1); | ||
287 | + // | ||
288 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
289 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
290 | + segmentAsNumbers2.add((long) 65041); | ||
291 | + segmentAsNumbers2.add((long) 65042); | ||
292 | + segmentAsNumbers2.add((long) 65043); | ||
293 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
294 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
295 | + pathSegments.add(pathSegment2); | ||
296 | + // | ||
297 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
298 | + | ||
299 | + // | ||
300 | + bgpRouteEntry = | ||
301 | + new BgpRouteEntry(bgpSession, | ||
302 | + IpPrefix.valueOf("0.0.0.0/0"), | ||
303 | + nextHopRouter, | ||
304 | + (byte) BgpConstants.Update.Origin.IGP, | ||
305 | + asPath, | ||
306 | + DEFAULT_LOCAL_PREF); | ||
307 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
308 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
309 | + // | ||
310 | + bgpRouteEntry = | ||
311 | + new BgpRouteEntry(bgpSession, | ||
312 | + IpPrefix.valueOf("20.0.0.0/8"), | ||
313 | + nextHopRouter, | ||
314 | + (byte) BgpConstants.Update.Origin.IGP, | ||
315 | + asPath, | ||
316 | + DEFAULT_LOCAL_PREF); | ||
317 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
318 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
319 | + // | ||
320 | + bgpRouteEntry = | ||
321 | + new BgpRouteEntry(bgpSession, | ||
322 | + IpPrefix.valueOf("30.0.0.0/16"), | ||
323 | + nextHopRouter, | ||
324 | + (byte) BgpConstants.Update.Origin.IGP, | ||
325 | + asPath, | ||
326 | + DEFAULT_LOCAL_PREF); | ||
327 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
328 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
329 | + // | ||
330 | + bgpRouteEntry = | ||
331 | + new BgpRouteEntry(bgpSession, | ||
332 | + IpPrefix.valueOf("40.0.0.0/24"), | ||
333 | + nextHopRouter, | ||
334 | + (byte) BgpConstants.Update.Origin.IGP, | ||
335 | + asPath, | ||
336 | + DEFAULT_LOCAL_PREF); | ||
337 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
338 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
339 | + // | ||
340 | + bgpRouteEntry = | ||
341 | + new BgpRouteEntry(bgpSession, | ||
342 | + IpPrefix.valueOf("50.0.0.0/32"), | ||
343 | + nextHopRouter, | ||
344 | + (byte) BgpConstants.Update.Origin.IGP, | ||
345 | + asPath, | ||
346 | + DEFAULT_LOCAL_PREF); | ||
347 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
348 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
349 | + | ||
350 | + // Delete some routes | ||
351 | + addedRoutes = new LinkedList<>(); | ||
352 | + withdrawnRoutes = new LinkedList<>(); | ||
353 | + withdrawnRoutes.add(IpPrefix.valueOf("0.0.0.0/0")); | ||
354 | + withdrawnRoutes.add(IpPrefix.valueOf("50.0.0.0/32")); | ||
355 | + | ||
356 | + // Write the routes | ||
357 | + message = peerChannelHandler.prepareBgpUpdate(nextHopRouter, | ||
358 | + addedRoutes, | ||
359 | + withdrawnRoutes); | ||
360 | + peerChannelHandler.savedCtx.getChannel().write(message); | ||
361 | + | ||
362 | + // Check that the routes have been received, processed and stored | ||
363 | + bgpRibIn = waitForBgpRibIn(bgpSession, 3); | ||
364 | + assertThat(bgpRibIn, hasSize(3)); | ||
365 | + bgpRoutes = waitForBgpRoutes(3); | ||
366 | + assertThat(bgpRoutes, hasSize(3)); | ||
367 | + // | ||
368 | + bgpRouteEntry = | ||
369 | + new BgpRouteEntry(bgpSession, | ||
370 | + IpPrefix.valueOf("20.0.0.0/8"), | ||
371 | + nextHopRouter, | ||
372 | + (byte) BgpConstants.Update.Origin.IGP, | ||
373 | + asPath, | ||
374 | + DEFAULT_LOCAL_PREF); | ||
375 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
376 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
377 | + // | ||
378 | + bgpRouteEntry = | ||
379 | + new BgpRouteEntry(bgpSession, | ||
380 | + IpPrefix.valueOf("30.0.0.0/16"), | ||
381 | + nextHopRouter, | ||
382 | + (byte) BgpConstants.Update.Origin.IGP, | ||
383 | + asPath, | ||
384 | + DEFAULT_LOCAL_PREF); | ||
385 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
386 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
387 | + // | ||
388 | + bgpRouteEntry = | ||
389 | + new BgpRouteEntry(bgpSession, | ||
390 | + IpPrefix.valueOf("40.0.0.0/24"), | ||
391 | + nextHopRouter, | ||
392 | + (byte) BgpConstants.Update.Origin.IGP, | ||
393 | + asPath, | ||
394 | + DEFAULT_LOCAL_PREF); | ||
395 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
396 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
397 | + | ||
398 | + // Close the channel and test there are no routes | ||
399 | + peerChannelHandler.closeChannel(); | ||
400 | + bgpRoutes = waitForBgpRoutes(0); | ||
401 | + assertThat(bgpRoutes, hasSize(0)); | ||
402 | + } | ||
403 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
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 java.util.ArrayList; | ||
8 | + | ||
9 | +import org.junit.Test; | ||
10 | + | ||
11 | +/** | ||
12 | + * Unit tests for the BgpRouteEntry.PathSegment class. | ||
13 | + */ | ||
14 | +public class PathSegmentTest { | ||
15 | + /** | ||
16 | + * Generates a Path Segment. | ||
17 | + * | ||
18 | + * @return a generated PathSegment | ||
19 | + */ | ||
20 | + private BgpRouteEntry.PathSegment generatePathSegment() { | ||
21 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
22 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
23 | + segmentAsNumbers.add((long) 1); | ||
24 | + segmentAsNumbers.add((long) 2); | ||
25 | + segmentAsNumbers.add((long) 3); | ||
26 | + BgpRouteEntry.PathSegment pathSegment = | ||
27 | + new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers); | ||
28 | + | ||
29 | + return pathSegment; | ||
30 | + } | ||
31 | + | ||
32 | + /** | ||
33 | + * Tests valid class constructor. | ||
34 | + */ | ||
35 | + @Test | ||
36 | + public void testConstructor() { | ||
37 | + BgpRouteEntry.PathSegment pathSegment = generatePathSegment(); | ||
38 | + | ||
39 | + String expectedString = | ||
40 | + "PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}"; | ||
41 | + assertThat(pathSegment.toString(), is(expectedString)); | ||
42 | + } | ||
43 | + | ||
44 | + /** | ||
45 | + * Tests invalid class constructor for null Segment AS Numbers. | ||
46 | + */ | ||
47 | + @Test(expected = NullPointerException.class) | ||
48 | + public void testInvalidConstructorNullSegmentAsNumbers() { | ||
49 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
50 | + ArrayList<Long> segmentAsNumbers = null; | ||
51 | + new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers); | ||
52 | + } | ||
53 | + | ||
54 | + /** | ||
55 | + * Tests getting the fields of a Path Segment. | ||
56 | + */ | ||
57 | + @Test | ||
58 | + public void testGetFields() { | ||
59 | + // Create the fields to compare against | ||
60 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
61 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
62 | + segmentAsNumbers.add((long) 1); | ||
63 | + segmentAsNumbers.add((long) 2); | ||
64 | + segmentAsNumbers.add((long) 3); | ||
65 | + | ||
66 | + // Generate the entry to test | ||
67 | + BgpRouteEntry.PathSegment pathSegment = generatePathSegment(); | ||
68 | + | ||
69 | + assertThat(pathSegment.getType(), is(pathSegmentType)); | ||
70 | + assertThat(pathSegment.getSegmentAsNumbers(), is(segmentAsNumbers)); | ||
71 | + } | ||
72 | + | ||
73 | + /** | ||
74 | + * Tests equality of {@link BgpRouteEntry.PathSegment}. | ||
75 | + */ | ||
76 | + @Test | ||
77 | + public void testEquality() { | ||
78 | + BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment(); | ||
79 | + BgpRouteEntry.PathSegment pathSegment2 = generatePathSegment(); | ||
80 | + | ||
81 | + assertThat(pathSegment1, is(pathSegment2)); | ||
82 | + } | ||
83 | + | ||
84 | + /** | ||
85 | + * Tests non-equality of {@link BgpRouteEntry.PathSegment}. | ||
86 | + */ | ||
87 | + @Test | ||
88 | + public void testNonEquality() { | ||
89 | + BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment(); | ||
90 | + | ||
91 | + // Setup Path Segment 2 | ||
92 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
93 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
94 | + segmentAsNumbers.add((long) 1); | ||
95 | + segmentAsNumbers.add((long) 22); // Different | ||
96 | + segmentAsNumbers.add((long) 3); | ||
97 | + // | ||
98 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
99 | + new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers); | ||
100 | + | ||
101 | + assertThat(pathSegment1, is(not(pathSegment2))); | ||
102 | + } | ||
103 | + | ||
104 | + /** | ||
105 | + * Tests object string representation. | ||
106 | + */ | ||
107 | + @Test | ||
108 | + public void testToString() { | ||
109 | + BgpRouteEntry.PathSegment pathSegment = generatePathSegment(); | ||
110 | + | ||
111 | + String expectedString = | ||
112 | + "PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}"; | ||
113 | + assertThat(pathSegment.toString(), is(expectedString)); | ||
114 | + } | ||
115 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import java.util.Collection; | ||
4 | + | ||
5 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
6 | +import org.jboss.netty.buffer.ChannelBuffers; | ||
7 | +import org.jboss.netty.channel.ChannelHandlerContext; | ||
8 | +import org.jboss.netty.channel.ChannelStateEvent; | ||
9 | +import org.jboss.netty.channel.SimpleChannelHandler; | ||
10 | +import org.onlab.packet.IpAddress; | ||
11 | +import org.onlab.packet.IpPrefix; | ||
12 | + | ||
13 | +/** | ||
14 | + * Class for handling the remote BGP Peer session. | ||
15 | + */ | ||
16 | +class TestBgpPeerChannelHandler extends SimpleChannelHandler { | ||
17 | + static final long PEER_AS = 65001; | ||
18 | + static final int PEER_HOLDTIME = 120; // 120 seconds | ||
19 | + final IpAddress bgpId; // The BGP ID | ||
20 | + final long localPref; // Local preference for routes | ||
21 | + final long multiExitDisc = 20; // MED value | ||
22 | + | ||
23 | + ChannelHandlerContext savedCtx; | ||
24 | + | ||
25 | + /** | ||
26 | + * Constructor for given BGP ID. | ||
27 | + * | ||
28 | + * @param bgpId the BGP ID to use | ||
29 | + * @param localPref the local preference for the routes to use | ||
30 | + */ | ||
31 | + TestBgpPeerChannelHandler(IpAddress bgpId, | ||
32 | + long localPref) { | ||
33 | + this.bgpId = bgpId; | ||
34 | + this.localPref = localPref; | ||
35 | + } | ||
36 | + | ||
37 | + /** | ||
38 | + * Closes the channel. | ||
39 | + */ | ||
40 | + void closeChannel() { | ||
41 | + savedCtx.getChannel().close(); | ||
42 | + } | ||
43 | + | ||
44 | + @Override | ||
45 | + public void channelConnected(ChannelHandlerContext ctx, | ||
46 | + ChannelStateEvent channelEvent) { | ||
47 | + this.savedCtx = ctx; | ||
48 | + // Prepare and transmit BGP OPEN message | ||
49 | + ChannelBuffer message = prepareBgpOpen(); | ||
50 | + ctx.getChannel().write(message); | ||
51 | + | ||
52 | + // Prepare and transmit BGP KEEPALIVE message | ||
53 | + message = prepareBgpKeepalive(); | ||
54 | + ctx.getChannel().write(message); | ||
55 | + } | ||
56 | + | ||
57 | + @Override | ||
58 | + public void channelDisconnected(ChannelHandlerContext ctx, | ||
59 | + ChannelStateEvent channelEvent) { | ||
60 | + // Nothing to do | ||
61 | + } | ||
62 | + | ||
63 | + /** | ||
64 | + * Prepares BGP OPEN message. | ||
65 | + * | ||
66 | + * @return the message to transmit (BGP header included) | ||
67 | + */ | ||
68 | + ChannelBuffer prepareBgpOpen() { | ||
69 | + ChannelBuffer message = | ||
70 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
71 | + message.writeByte(BgpConstants.BGP_VERSION); | ||
72 | + message.writeShort((int) PEER_AS); | ||
73 | + message.writeShort(PEER_HOLDTIME); | ||
74 | + message.writeInt(bgpId.toInt()); | ||
75 | + message.writeByte(0); // No Optional Parameters | ||
76 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message); | ||
77 | + } | ||
78 | + | ||
79 | + /** | ||
80 | + * Prepares BGP UPDATE message. | ||
81 | + * | ||
82 | + * @param nextHopRouter the next-hop router address for the routes to add | ||
83 | + * @param addedRoutes the routes to add | ||
84 | + * @param withdrawnRoutes the routes to withdraw | ||
85 | + * @return the message to transmit (BGP header included) | ||
86 | + */ | ||
87 | + ChannelBuffer prepareBgpUpdate(IpAddress nextHopRouter, | ||
88 | + Collection<IpPrefix> addedRoutes, | ||
89 | + Collection<IpPrefix> withdrawnRoutes) { | ||
90 | + int attrFlags; | ||
91 | + ChannelBuffer message = | ||
92 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
93 | + ChannelBuffer pathAttributes = | ||
94 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
95 | + | ||
96 | + // Encode the Withdrawn Routes | ||
97 | + ChannelBuffer encodedPrefixes = encodePackedPrefixes(withdrawnRoutes); | ||
98 | + message.writeShort(encodedPrefixes.readableBytes()); | ||
99 | + message.writeBytes(encodedPrefixes); | ||
100 | + | ||
101 | + // Encode the Path Attributes | ||
102 | + // ORIGIN: IGP | ||
103 | + attrFlags = 0x40; // Transitive flag | ||
104 | + pathAttributes.writeByte(attrFlags); | ||
105 | + pathAttributes.writeByte(BgpConstants.Update.Origin.TYPE); | ||
106 | + pathAttributes.writeByte(1); // Data length | ||
107 | + pathAttributes.writeByte(BgpConstants.Update.Origin.IGP); | ||
108 | + // AS_PATH: Two Path Segments of 3 ASes each | ||
109 | + attrFlags = 0x40; // Transitive flag | ||
110 | + pathAttributes.writeByte(attrFlags); | ||
111 | + pathAttributes.writeByte(BgpConstants.Update.AsPath.TYPE); | ||
112 | + pathAttributes.writeByte(16); // Data length | ||
113 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
114 | + pathAttributes.writeByte(pathSegmentType1); | ||
115 | + pathAttributes.writeByte(3); // Three ASes | ||
116 | + pathAttributes.writeShort(65010); // AS=65010 | ||
117 | + pathAttributes.writeShort(65020); // AS=65020 | ||
118 | + pathAttributes.writeShort(65030); // AS=65030 | ||
119 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
120 | + pathAttributes.writeByte(pathSegmentType2); | ||
121 | + pathAttributes.writeByte(3); // Three ASes | ||
122 | + pathAttributes.writeShort(65041); // AS=65041 | ||
123 | + pathAttributes.writeShort(65042); // AS=65042 | ||
124 | + pathAttributes.writeShort(65043); // AS=65043 | ||
125 | + // NEXT_HOP: nextHopRouter | ||
126 | + attrFlags = 0x40; // Transitive flag | ||
127 | + pathAttributes.writeByte(attrFlags); | ||
128 | + pathAttributes.writeByte(BgpConstants.Update.NextHop.TYPE); | ||
129 | + pathAttributes.writeByte(4); // Data length | ||
130 | + pathAttributes.writeInt(nextHopRouter.toInt()); // Next-hop router | ||
131 | + // LOCAL_PREF: localPref | ||
132 | + attrFlags = 0x40; // Transitive flag | ||
133 | + pathAttributes.writeByte(attrFlags); | ||
134 | + pathAttributes.writeByte(BgpConstants.Update.LocalPref.TYPE); | ||
135 | + pathAttributes.writeByte(4); // Data length | ||
136 | + pathAttributes.writeInt((int) localPref); // Preference value | ||
137 | + // MULTI_EXIT_DISC: multiExitDisc | ||
138 | + attrFlags = 0x80; // Optional | ||
139 | + // Non-Transitive flag | ||
140 | + pathAttributes.writeByte(attrFlags); | ||
141 | + pathAttributes.writeByte(BgpConstants.Update.MultiExitDisc.TYPE); | ||
142 | + pathAttributes.writeByte(4); // Data length | ||
143 | + pathAttributes.writeInt((int) multiExitDisc); // Preference value | ||
144 | + // The NLRI prefixes | ||
145 | + encodedPrefixes = encodePackedPrefixes(addedRoutes); | ||
146 | + | ||
147 | + // Write the Path Attributes, beginning with its length | ||
148 | + message.writeShort(pathAttributes.readableBytes()); | ||
149 | + message.writeBytes(pathAttributes); | ||
150 | + message.writeBytes(encodedPrefixes); | ||
151 | + | ||
152 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_UPDATE, message); | ||
153 | + } | ||
154 | + | ||
155 | + /** | ||
156 | + * Encodes a collection of IPv4 network prefixes in a packed format. | ||
157 | + * <p> | ||
158 | + * The IPv4 prefixes are encoded in the form: | ||
159 | + * <Length, Prefix> where Length is the length in bits of the IPv4 prefix, | ||
160 | + * and Prefix is the IPv4 prefix (padded with trailing bits to the end | ||
161 | + * of an octet). | ||
162 | + * | ||
163 | + * @param prefixes the prefixes to encode | ||
164 | + * @return the buffer with the encoded prefixes | ||
165 | + */ | ||
166 | + private ChannelBuffer encodePackedPrefixes(Collection<IpPrefix> prefixes) { | ||
167 | + ChannelBuffer message = | ||
168 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
169 | + | ||
170 | + // Write each of the prefixes | ||
171 | + for (IpPrefix prefix : prefixes) { | ||
172 | + int prefixBitlen = prefix.prefixLength(); | ||
173 | + int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up | ||
174 | + message.writeByte(prefixBitlen); | ||
175 | + | ||
176 | + IpAddress address = prefix.toIpAddress(); | ||
177 | + long value = address.toInt() & 0xffffffffL; | ||
178 | + for (int i = 0; i < IpAddress.INET_LEN; i++) { | ||
179 | + if (prefixBytelen-- == 0) { | ||
180 | + break; | ||
181 | + } | ||
182 | + long nextByte = | ||
183 | + (value >> ((IpAddress.INET_LEN - i - 1) * 8)) & 0xff; | ||
184 | + message.writeByte((int) nextByte); | ||
185 | + } | ||
186 | + } | ||
187 | + | ||
188 | + return message; | ||
189 | + } | ||
190 | + | ||
191 | + /** | ||
192 | + * Prepares BGP KEEPALIVE message. | ||
193 | + * | ||
194 | + * @return the message to transmit (BGP header included) | ||
195 | + */ | ||
196 | + ChannelBuffer prepareBgpKeepalive() { | ||
197 | + ChannelBuffer message = | ||
198 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
199 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message); | ||
200 | + } | ||
201 | + | ||
202 | + /** | ||
203 | + * Prepares BGP NOTIFICATION message. | ||
204 | + * | ||
205 | + * @param errorCode the BGP NOTIFICATION Error Code | ||
206 | + * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable, | ||
207 | + * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC | ||
208 | + * @param payload the BGP NOTIFICATION Data if applicable, otherwise null | ||
209 | + * @return the message to transmit (BGP header included) | ||
210 | + */ | ||
211 | + ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode, | ||
212 | + ChannelBuffer data) { | ||
213 | + ChannelBuffer message = | ||
214 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
215 | + // Prepare the NOTIFICATION message payload | ||
216 | + message.writeByte(errorCode); | ||
217 | + message.writeByte(errorSubcode); | ||
218 | + if (data != null) { | ||
219 | + message.writeBytes(data); | ||
220 | + } | ||
221 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message); | ||
222 | + } | ||
223 | + | ||
224 | + /** | ||
225 | + * Prepares BGP message. | ||
226 | + * | ||
227 | + * @param type the BGP message type | ||
228 | + * @param payload the message payload to transmit (BGP header excluded) | ||
229 | + * @return the message to transmit (BGP header included) | ||
230 | + */ | ||
231 | + private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) { | ||
232 | + ChannelBuffer message = | ||
233 | + ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH + | ||
234 | + payload.readableBytes()); | ||
235 | + | ||
236 | + // Write the marker | ||
237 | + for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) { | ||
238 | + message.writeByte(0xff); | ||
239 | + } | ||
240 | + | ||
241 | + // Write the rest of the BGP header | ||
242 | + message.writeShort(BgpConstants.BGP_HEADER_LENGTH + | ||
243 | + payload.readableBytes()); | ||
244 | + message.writeByte(type); | ||
245 | + | ||
246 | + // Write the payload | ||
247 | + message.writeBytes(payload); | ||
248 | + return message; | ||
249 | + } | ||
250 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import java.util.concurrent.CountDownLatch; | ||
4 | + | ||
5 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
6 | +import org.jboss.netty.channel.Channel; | ||
7 | +import org.jboss.netty.channel.ChannelHandlerContext; | ||
8 | +import org.jboss.netty.handler.codec.frame.FrameDecoder; | ||
9 | +import org.onlab.packet.IpAddress; | ||
10 | + | ||
11 | +/** | ||
12 | + * Class for handling the decoding of the BGP messages at the remote | ||
13 | + * BGP peer session. | ||
14 | + */ | ||
15 | +class TestBgpPeerFrameDecoder extends FrameDecoder { | ||
16 | + int remoteBgpVersion; // 1 octet | ||
17 | + long remoteAs; // 2 octets | ||
18 | + long remoteHoldtime; // 2 octets | ||
19 | + IpAddress remoteBgpIdentifier; // 4 octets -> IPv4 address | ||
20 | + | ||
21 | + final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1); | ||
22 | + final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1); | ||
23 | + | ||
24 | + @Override | ||
25 | + protected Object decode(ChannelHandlerContext ctx, | ||
26 | + Channel channel, | ||
27 | + ChannelBuffer buf) throws Exception { | ||
28 | + // Test for minimum length of the BGP message | ||
29 | + if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) { | ||
30 | + // No enough data received | ||
31 | + return null; | ||
32 | + } | ||
33 | + | ||
34 | + // | ||
35 | + // Mark the current buffer position in case we haven't received | ||
36 | + // the whole message. | ||
37 | + // | ||
38 | + buf.markReaderIndex(); | ||
39 | + | ||
40 | + // | ||
41 | + // Read and check the BGP message Marker field: it must be all ones | ||
42 | + // | ||
43 | + byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH]; | ||
44 | + buf.readBytes(marker); | ||
45 | + for (int i = 0; i < marker.length; i++) { | ||
46 | + if (marker[i] != (byte) 0xff) { | ||
47 | + // ERROR: Connection Not Synchronized. Close the channel. | ||
48 | + ctx.getChannel().close(); | ||
49 | + return null; | ||
50 | + } | ||
51 | + } | ||
52 | + | ||
53 | + // | ||
54 | + // Read and check the BGP message Length field | ||
55 | + // | ||
56 | + int length = buf.readUnsignedShort(); | ||
57 | + if ((length < BgpConstants.BGP_HEADER_LENGTH) || | ||
58 | + (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) { | ||
59 | + // ERROR: Bad Message Length. Close the channel. | ||
60 | + ctx.getChannel().close(); | ||
61 | + return null; | ||
62 | + } | ||
63 | + | ||
64 | + // | ||
65 | + // Test whether the rest of the message is received: | ||
66 | + // So far we have read the Marker (16 octets) and the | ||
67 | + // Length (2 octets) fields. | ||
68 | + // | ||
69 | + int remainingMessageLen = | ||
70 | + length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2; | ||
71 | + if (buf.readableBytes() < remainingMessageLen) { | ||
72 | + // No enough data received | ||
73 | + buf.resetReaderIndex(); | ||
74 | + return null; | ||
75 | + } | ||
76 | + | ||
77 | + // | ||
78 | + // Read the BGP message Type field, and process based on that type | ||
79 | + // | ||
80 | + int type = buf.readUnsignedByte(); | ||
81 | + remainingMessageLen--; // Adjust after reading the type | ||
82 | + ChannelBuffer message = buf.readBytes(remainingMessageLen); | ||
83 | + | ||
84 | + // | ||
85 | + // Process the remaining of the message based on the message type | ||
86 | + // | ||
87 | + switch (type) { | ||
88 | + case BgpConstants.BGP_TYPE_OPEN: | ||
89 | + processBgpOpen(ctx, message); | ||
90 | + break; | ||
91 | + case BgpConstants.BGP_TYPE_UPDATE: | ||
92 | + // NOTE: Not used as part of the test, because ONOS does not | ||
93 | + // originate UPDATE messages. | ||
94 | + break; | ||
95 | + case BgpConstants.BGP_TYPE_NOTIFICATION: | ||
96 | + // NOTE: Not used as part of the testing (yet) | ||
97 | + break; | ||
98 | + case BgpConstants.BGP_TYPE_KEEPALIVE: | ||
99 | + processBgpKeepalive(ctx, message); | ||
100 | + break; | ||
101 | + default: | ||
102 | + // ERROR: Bad Message Type. Close the channel. | ||
103 | + ctx.getChannel().close(); | ||
104 | + return null; | ||
105 | + } | ||
106 | + | ||
107 | + return null; | ||
108 | + } | ||
109 | + | ||
110 | + /** | ||
111 | + * Processes BGP OPEN message. | ||
112 | + * | ||
113 | + * @param ctx the Channel Handler Context. | ||
114 | + * @param message the message to process. | ||
115 | + */ | ||
116 | + private void processBgpOpen(ChannelHandlerContext ctx, | ||
117 | + ChannelBuffer message) { | ||
118 | + int minLength = | ||
119 | + BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH; | ||
120 | + if (message.readableBytes() < minLength) { | ||
121 | + // ERROR: Bad Message Length. Close the channel. | ||
122 | + ctx.getChannel().close(); | ||
123 | + return; | ||
124 | + } | ||
125 | + | ||
126 | + // | ||
127 | + // Parse the OPEN message | ||
128 | + // | ||
129 | + remoteBgpVersion = message.readUnsignedByte(); | ||
130 | + remoteAs = message.readUnsignedShort(); | ||
131 | + remoteHoldtime = message.readUnsignedShort(); | ||
132 | + remoteBgpIdentifier = IpAddress.valueOf((int) message.readUnsignedInt()); | ||
133 | + // Optional Parameters | ||
134 | + int optParamLen = message.readUnsignedByte(); | ||
135 | + if (message.readableBytes() < optParamLen) { | ||
136 | + // ERROR: Bad Message Length. Close the channel. | ||
137 | + ctx.getChannel().close(); | ||
138 | + return; | ||
139 | + } | ||
140 | + message.readBytes(optParamLen); // NOTE: data ignored | ||
141 | + | ||
142 | + // BGP OPEN message successfully received | ||
143 | + receivedOpenMessageLatch.countDown(); | ||
144 | + } | ||
145 | + | ||
146 | + /** | ||
147 | + * Processes BGP KEEPALIVE message. | ||
148 | + * | ||
149 | + * @param ctx the Channel Handler Context. | ||
150 | + * @param message the message to process. | ||
151 | + */ | ||
152 | + private void processBgpKeepalive(ChannelHandlerContext ctx, | ||
153 | + ChannelBuffer message) { | ||
154 | + if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH != | ||
155 | + BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) { | ||
156 | + // ERROR: Bad Message Length. Close the channel. | ||
157 | + ctx.getChannel().close(); | ||
158 | + return; | ||
159 | + } | ||
160 | + // BGP KEEPALIVE message successfully received | ||
161 | + receivedKeepaliveMessageLatch.countDown(); | ||
162 | + } | ||
163 | +} |
... | @@ -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 | + .set("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 | } | ... | ... |
... | @@ -12,7 +12,7 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -12,7 +12,7 @@ import static com.google.common.base.Preconditions.checkNotNull; |
12 | /** | 12 | /** |
13 | * Abstraction of end-station to end-station bidirectional connectivity. | 13 | * Abstraction of end-station to end-station bidirectional connectivity. |
14 | */ | 14 | */ |
15 | -public class HostToHostIntent extends ConnectivityIntent { | 15 | +public final class HostToHostIntent extends ConnectivityIntent { |
16 | 16 | ||
17 | private final HostId one; | 17 | private final HostId one; |
18 | private final HostId two; | 18 | private final HostId two; | ... | ... |
... | @@ -14,7 +14,7 @@ import com.google.common.base.MoreObjects; | ... | @@ -14,7 +14,7 @@ import com.google.common.base.MoreObjects; |
14 | * Abstraction of a connectivity intent that is implemented by a set of path | 14 | * Abstraction of a connectivity intent that is implemented by a set of path |
15 | * segments. | 15 | * segments. |
16 | */ | 16 | */ |
17 | -public class LinkCollectionIntent extends ConnectivityIntent implements InstallableIntent { | 17 | +public final class LinkCollectionIntent extends ConnectivityIntent implements InstallableIntent { |
18 | 18 | ||
19 | private final Set<Link> links; | 19 | private final Set<Link> links; |
20 | 20 | ||
... | @@ -46,6 +46,12 @@ public class LinkCollectionIntent extends ConnectivityIntent implements Installa | ... | @@ -46,6 +46,12 @@ public class LinkCollectionIntent extends ConnectivityIntent implements Installa |
46 | return links; | 46 | return links; |
47 | } | 47 | } |
48 | 48 | ||
49 | + /** | ||
50 | + * Returns the set of links that represent the network connections needed | ||
51 | + * by this intent. | ||
52 | + * | ||
53 | + * @return Set of links for the network hops needed by this intent | ||
54 | + */ | ||
49 | public Set<Link> links() { | 55 | public Set<Link> links() { |
50 | return links; | 56 | return links; |
51 | } | 57 | } | ... | ... |
... | @@ -15,7 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -15,7 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull; |
15 | /** | 15 | /** |
16 | * Abstraction of multiple source to single destination connectivity intent. | 16 | * Abstraction of multiple source to single destination connectivity intent. |
17 | */ | 17 | */ |
18 | -public class MultiPointToSinglePointIntent extends ConnectivityIntent { | 18 | +public final class MultiPointToSinglePointIntent extends ConnectivityIntent { |
19 | 19 | ||
20 | private final Set<ConnectPoint> ingressPoints; | 20 | private final Set<ConnectPoint> ingressPoints; |
21 | private final ConnectPoint egressPoint; | 21 | private final ConnectPoint egressPoint; | ... | ... |
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 | ... | ... |
... | @@ -48,10 +48,16 @@ public final class NetTestTools { | ... | @@ -48,10 +48,16 @@ public final class NetTestTools { |
48 | new HashSet<IpPrefix>()); | 48 | new HashSet<IpPrefix>()); |
49 | } | 49 | } |
50 | 50 | ||
51 | + // Short-hand for creating a connection point. | ||
52 | + public static ConnectPoint connectPoint(String id, int port) { | ||
53 | + return new ConnectPoint(did(id), portNumber(port)); | ||
54 | + } | ||
55 | + | ||
51 | // Short-hand for creating a link. | 56 | // Short-hand for creating a link. |
52 | public static Link link(String src, int sp, String dst, int dp) { | 57 | public static Link link(String src, int sp, String dst, int dp) { |
53 | - return new DefaultLink(PID, new ConnectPoint(did(src), portNumber(sp)), | 58 | + return new DefaultLink(PID, |
54 | - new ConnectPoint(did(dst), portNumber(dp)), | 59 | + connectPoint(src, sp), |
60 | + connectPoint(dst, dp), | ||
55 | Link.Type.DIRECT); | 61 | Link.Type.DIRECT); |
56 | } | 62 | } |
57 | 63 | ... | ... |
... | @@ -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 | ... | ... |
... | @@ -6,15 +6,15 @@ | ... | @@ -6,15 +6,15 @@ |
6 | 6 | ||
7 | <parent> | 7 | <parent> |
8 | <groupId>org.onlab.onos</groupId> | 8 | <groupId>org.onlab.onos</groupId> |
9 | - <artifactId>onos-core-hz</artifactId> | 9 | + <artifactId>onos-core</artifactId> |
10 | <version>1.0.0-SNAPSHOT</version> | 10 | <version>1.0.0-SNAPSHOT</version> |
11 | <relativePath>../pom.xml</relativePath> | 11 | <relativePath>../pom.xml</relativePath> |
12 | </parent> | 12 | </parent> |
13 | 13 | ||
14 | - <artifactId>onos-core-hz-net</artifactId> | 14 | + <artifactId>onos-json</artifactId> |
15 | <packaging>bundle</packaging> | 15 | <packaging>bundle</packaging> |
16 | 16 | ||
17 | - <description>ONOS Hazelcast based distributed store subsystems</description> | 17 | + <description>ONOS JSON encode/decode facilities</description> |
18 | 18 | ||
19 | <dependencies> | 19 | <dependencies> |
20 | <dependency> | 20 | <dependency> |
... | @@ -23,24 +23,22 @@ | ... | @@ -23,24 +23,22 @@ |
23 | </dependency> | 23 | </dependency> |
24 | <dependency> | 24 | <dependency> |
25 | <groupId>org.onlab.onos</groupId> | 25 | <groupId>org.onlab.onos</groupId> |
26 | - <artifactId>onos-core-hz-common</artifactId> | 26 | + <artifactId>onos-api</artifactId> |
27 | - <version>${project.version}</version> | 27 | + <classifier>tests</classifier> |
28 | + <scope>test</scope> | ||
28 | </dependency> | 29 | </dependency> |
30 | + | ||
29 | <dependency> | 31 | <dependency> |
30 | <groupId>org.onlab.onos</groupId> | 32 | <groupId>org.onlab.onos</groupId> |
31 | - <artifactId>onos-core-hz-common</artifactId> | 33 | + <artifactId>onos-core-trivial</artifactId> |
32 | - <classifier>tests</classifier> | ||
33 | - <scope>test</scope> | ||
34 | <version>${project.version}</version> | 34 | <version>${project.version}</version> |
35 | + <scope>test</scope> | ||
35 | </dependency> | 36 | </dependency> |
37 | + | ||
36 | <dependency> | 38 | <dependency> |
37 | <groupId>org.apache.felix</groupId> | 39 | <groupId>org.apache.felix</groupId> |
38 | <artifactId>org.apache.felix.scr.annotations</artifactId> | 40 | <artifactId>org.apache.felix.scr.annotations</artifactId> |
39 | </dependency> | 41 | </dependency> |
40 | - <dependency> | ||
41 | - <groupId>com.hazelcast</groupId> | ||
42 | - <artifactId>hazelcast</artifactId> | ||
43 | - </dependency> | ||
44 | </dependencies> | 42 | </dependencies> |
45 | 43 | ||
46 | <build> | 44 | <build> | ... | ... |
... | @@ -36,26 +36,9 @@ | ... | @@ -36,26 +36,9 @@ |
36 | <scope>test</scope> | 36 | <scope>test</scope> |
37 | </dependency> | 37 | </dependency> |
38 | 38 | ||
39 | - <dependency> | ||
40 | - <groupId>org.easymock</groupId> | ||
41 | - <artifactId>easymock</artifactId> | ||
42 | - <scope>test</scope> | ||
43 | - </dependency> | ||
44 | - | ||
45 | - <!-- TODO Consider removing store dependency. | ||
46 | - Currently required for DistributedDeviceManagerTest. --> | ||
47 | <dependency> | 39 | <dependency> |
48 | - <groupId>org.onlab.onos</groupId> | 40 | + <groupId>org.easymock</groupId> |
49 | - <artifactId>onos-core-hz-net</artifactId> | 41 | + <artifactId>easymock</artifactId> |
50 | - <version>${project.version}</version> | ||
51 | - <scope>test</scope> | ||
52 | - </dependency> | ||
53 | - <dependency> | ||
54 | - <groupId>org.onlab.onos</groupId> | ||
55 | - <!-- FIXME: should be somewhere else --> | ||
56 | - <artifactId>onos-core-hz-common</artifactId> | ||
57 | - <version>${project.version}</version> | ||
58 | - <classifier>tests</classifier> | ||
59 | <scope>test</scope> | 42 | <scope>test</scope> |
60 | </dependency> | 43 | </dependency> |
61 | 44 | ... | ... |
... | @@ -41,7 +41,7 @@ public class HostToHostIntentCompiler | ... | @@ -41,7 +41,7 @@ public class HostToHostIntentCompiler |
41 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 41 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
42 | protected HostService hostService; | 42 | protected HostService hostService; |
43 | 43 | ||
44 | - private IdGenerator<IntentId> intentIdGenerator; | 44 | + protected IdGenerator<IntentId> intentIdGenerator; |
45 | 45 | ||
46 | @Activate | 46 | @Activate |
47 | public void activate() { | 47 | public void activate() { | ... | ... |
... | @@ -37,7 +37,7 @@ public class MultiPointToSinglePointIntentCompiler | ... | @@ -37,7 +37,7 @@ public class MultiPointToSinglePointIntentCompiler |
37 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 37 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
38 | protected PathService pathService; | 38 | protected PathService pathService; |
39 | 39 | ||
40 | - private IdGenerator<IntentId> intentIdGenerator; | 40 | + protected IdGenerator<IntentId> intentIdGenerator; |
41 | 41 | ||
42 | @Activate | 42 | @Activate |
43 | public void activate() { | 43 | public void activate() { | ... | ... |
... | @@ -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 | } | ... | ... |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import java.util.ArrayList; | ||
4 | +import java.util.Arrays; | ||
5 | +import java.util.Collections; | ||
6 | +import java.util.HashSet; | ||
7 | +import java.util.List; | ||
8 | +import java.util.Set; | ||
9 | + | ||
10 | +import org.onlab.onos.net.ElementId; | ||
11 | +import org.onlab.onos.net.Path; | ||
12 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
13 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
14 | +import org.onlab.onos.net.flow.criteria.Criterion; | ||
15 | +import org.onlab.onos.net.flow.instructions.Instruction; | ||
16 | +import org.onlab.onos.net.topology.LinkWeight; | ||
17 | +import org.onlab.onos.net.topology.PathService; | ||
18 | + | ||
19 | +import static org.onlab.onos.net.NetTestTools.createPath; | ||
20 | + | ||
21 | +/** | ||
22 | + * Common mocks used by the intent framework tests. | ||
23 | + */ | ||
24 | +public class IntentTestsMocks { | ||
25 | + /** | ||
26 | + * Mock traffic selector class used for satisfying API requirements. | ||
27 | + */ | ||
28 | + public static class MockSelector implements TrafficSelector { | ||
29 | + @Override | ||
30 | + public Set<Criterion> criteria() { | ||
31 | + return new HashSet<>(); | ||
32 | + } | ||
33 | + } | ||
34 | + | ||
35 | + /** | ||
36 | + * Mock traffic treatment class used for satisfying API requirements. | ||
37 | + */ | ||
38 | + public static class MockTreatment implements TrafficTreatment { | ||
39 | + @Override | ||
40 | + public List<Instruction> instructions() { | ||
41 | + return new ArrayList<>(); | ||
42 | + } | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Mock path service for creating paths within the test. | ||
47 | + */ | ||
48 | + public static class MockPathService implements PathService { | ||
49 | + | ||
50 | + final String[] pathHops; | ||
51 | + final String[] reversePathHops; | ||
52 | + | ||
53 | + /** | ||
54 | + * Constructor that provides a set of hops to mock. | ||
55 | + * | ||
56 | + * @param pathHops path hops to mock | ||
57 | + */ | ||
58 | + public MockPathService(String[] pathHops) { | ||
59 | + this.pathHops = pathHops; | ||
60 | + String[] reversed = pathHops.clone(); | ||
61 | + Collections.reverse(Arrays.asList(reversed)); | ||
62 | + reversePathHops = reversed; | ||
63 | + } | ||
64 | + | ||
65 | + @Override | ||
66 | + public Set<Path> getPaths(ElementId src, ElementId dst) { | ||
67 | + Set<Path> result = new HashSet<>(); | ||
68 | + | ||
69 | + String[] allHops = new String[pathHops.length]; | ||
70 | + | ||
71 | + if (src.toString().endsWith(pathHops[0])) { | ||
72 | + System.arraycopy(pathHops, 0, allHops, 0, pathHops.length); | ||
73 | + } else { | ||
74 | + System.arraycopy(reversePathHops, 0, allHops, 0, pathHops.length); | ||
75 | + } | ||
76 | + | ||
77 | + result.add(createPath(allHops)); | ||
78 | + return result; | ||
79 | + } | ||
80 | + | ||
81 | + @Override | ||
82 | + public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) { | ||
83 | + return getPaths(src, dst); | ||
84 | + } | ||
85 | + } | ||
86 | +} |
core/net/src/test/java/org/onlab/onos/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java
0 → 100644
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import java.util.Collection; | ||
4 | + | ||
5 | +import org.hamcrest.Description; | ||
6 | +import org.hamcrest.TypeSafeMatcher; | ||
7 | +import org.onlab.onos.net.Link; | ||
8 | + | ||
9 | +/** | ||
10 | + * Matcher to determine if a Collection of Links contains a path between a source | ||
11 | + * and a destination. | ||
12 | + */ | ||
13 | +public class LinksHaveEntryWithSourceDestinationPairMatcher extends | ||
14 | + TypeSafeMatcher<Collection<Link>> { | ||
15 | + private final String source; | ||
16 | + private final String destination; | ||
17 | + | ||
18 | + /** | ||
19 | + * Creates a matcher for a given path represented by a source and | ||
20 | + * a destination. | ||
21 | + * | ||
22 | + * @param source string identifier for the source of the path | ||
23 | + * @param destination string identifier for the destination of the path | ||
24 | + */ | ||
25 | + LinksHaveEntryWithSourceDestinationPairMatcher(String source, | ||
26 | + String destination) { | ||
27 | + this.source = source; | ||
28 | + this.destination = destination; | ||
29 | + } | ||
30 | + | ||
31 | + @Override | ||
32 | + public boolean matchesSafely(Collection<Link> links) { | ||
33 | + for (Link link : links) { | ||
34 | + if (link.src().elementId().toString().endsWith(source) && | ||
35 | + link.dst().elementId().toString().endsWith(destination)) { | ||
36 | + return true; | ||
37 | + } | ||
38 | + } | ||
39 | + | ||
40 | + return false; | ||
41 | + } | ||
42 | + | ||
43 | + @Override | ||
44 | + public void describeTo(Description description) { | ||
45 | + description.appendText("link lookup for source \""); | ||
46 | + description.appendText(source); | ||
47 | + description.appendText(" and destination "); | ||
48 | + description.appendText(destination); | ||
49 | + description.appendText("\""); | ||
50 | + } | ||
51 | + | ||
52 | + @Override | ||
53 | + public void describeMismatchSafely(Collection<Link> links, | ||
54 | + Description mismatchDescription) { | ||
55 | + mismatchDescription.appendText("was "). | ||
56 | + appendText(links.toString()); | ||
57 | + } | ||
58 | + | ||
59 | + /** | ||
60 | + * Creates a link has path matcher. | ||
61 | + * | ||
62 | + * @param source string identifier for the source of the path | ||
63 | + * @param destination string identifier for the destination of the path | ||
64 | + * @return matcher to match the path | ||
65 | + */ | ||
66 | + public static LinksHaveEntryWithSourceDestinationPairMatcher linksHasPath( | ||
67 | + String source, | ||
68 | + String destination) { | ||
69 | + return new LinksHaveEntryWithSourceDestinationPairMatcher(source, | ||
70 | + destination); | ||
71 | + } | ||
72 | +} | ||
73 | + |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.Test; | ||
4 | +import org.onlab.onos.net.HostId; | ||
5 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
6 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
7 | + | ||
8 | +import static org.hamcrest.MatcherAssert.assertThat; | ||
9 | +import static org.hamcrest.Matchers.equalTo; | ||
10 | +import static org.hamcrest.Matchers.is; | ||
11 | +import static org.hamcrest.Matchers.not; | ||
12 | +import static org.onlab.onos.net.NetTestTools.hid; | ||
13 | + | ||
14 | +/** | ||
15 | + * Unit tests for the HostToHostIntent class. | ||
16 | + */ | ||
17 | +public class TestHostToHostIntent { | ||
18 | + | ||
19 | + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); | ||
20 | + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); | ||
21 | + | ||
22 | + private HostToHostIntent makeHostToHost(long id, HostId one, HostId two) { | ||
23 | + return new HostToHostIntent(new IntentId(id), | ||
24 | + one, | ||
25 | + two, | ||
26 | + selector, | ||
27 | + treatment); | ||
28 | + } | ||
29 | + | ||
30 | + /** | ||
31 | + * Tests the equals() method where two HostToHostIntents have references | ||
32 | + * to the same hosts. These should compare equal. | ||
33 | + */ | ||
34 | + @Test | ||
35 | + public void testSameEquals() { | ||
36 | + | ||
37 | + HostId one = hid("00:00:00:00:00:01/-1"); | ||
38 | + HostId two = hid("00:00:00:00:00:02/-1"); | ||
39 | + HostToHostIntent i1 = makeHostToHost(12, one, two); | ||
40 | + HostToHostIntent i2 = makeHostToHost(12, one, two); | ||
41 | + | ||
42 | + assertThat(i1, is(equalTo(i2))); | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Tests the equals() method where two HostToHostIntents have references | ||
47 | + * to different Hosts. These should compare not equal. | ||
48 | + */ | ||
49 | + @Test | ||
50 | + public void testLinksDifferentEquals() { | ||
51 | + | ||
52 | + HostId one = hid("00:00:00:00:00:01/-1"); | ||
53 | + HostId two = hid("00:00:00:00:00:02/-1"); | ||
54 | + HostToHostIntent i1 = makeHostToHost(12, one, two); | ||
55 | + HostToHostIntent i2 = makeHostToHost(12, two, one); | ||
56 | + | ||
57 | + assertThat(i1, is(not(equalTo(i2)))); | ||
58 | + } | ||
59 | + | ||
60 | + /** | ||
61 | + * Tests the equals() method where two HostToHostIntents have different | ||
62 | + * ids. These should compare not equal. | ||
63 | + */ | ||
64 | + | ||
65 | + @Test | ||
66 | + public void testBaseDifferentEquals() { | ||
67 | + HostId one = hid("00:00:00:00:00:01/-1"); | ||
68 | + HostId two = hid("00:00:00:00:00:02/-1"); | ||
69 | + HostToHostIntent i1 = makeHostToHost(12, one, two); | ||
70 | + HostToHostIntent i2 = makeHostToHost(11, one, two); | ||
71 | + | ||
72 | + assertThat(i1, is(not(equalTo(i2)))); | ||
73 | + } | ||
74 | + | ||
75 | + /** | ||
76 | + * Tests that the hashCode() values for two equivalent HostToHostIntent | ||
77 | + * objects are the same. | ||
78 | + */ | ||
79 | + | ||
80 | + @Test | ||
81 | + public void testHashCodeEquals() { | ||
82 | + HostId one = hid("00:00:00:00:00:01/-1"); | ||
83 | + HostId two = hid("00:00:00:00:00:02/-1"); | ||
84 | + HostToHostIntent i1 = makeHostToHost(12, one, two); | ||
85 | + HostToHostIntent i2 = makeHostToHost(12, one, two); | ||
86 | + | ||
87 | + assertThat(i1.hashCode(), is(equalTo(i2.hashCode()))); | ||
88 | + } | ||
89 | + | ||
90 | + /** | ||
91 | + * Tests that the hashCode() values for two distinct LinkCollectionIntent | ||
92 | + * objects are different. | ||
93 | + */ | ||
94 | + | ||
95 | + @Test | ||
96 | + public void testHashCodeDifferent() { | ||
97 | + HostId one = hid("00:00:00:00:00:01/-1"); | ||
98 | + HostId two = hid("00:00:00:00:00:02/-1"); | ||
99 | + HostToHostIntent i1 = makeHostToHost(12, one, two); | ||
100 | + HostToHostIntent i2 = makeHostToHost(112, one, two); | ||
101 | + | ||
102 | + assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode())))); | ||
103 | + } | ||
104 | + | ||
105 | + /** | ||
106 | + * Checks that the HostToHostIntent class is immutable. | ||
107 | + */ | ||
108 | + @Test | ||
109 | + public void checkImmutability() { | ||
110 | + ImmutableClassChecker.assertThatClassIsImmutable(HostToHostIntent.class); | ||
111 | + } | ||
112 | +} |
1 | package org.onlab.onos.net.intent; | 1 | package org.onlab.onos.net.intent; |
2 | 2 | ||
3 | -import java.util.ArrayList; | ||
4 | import java.util.HashSet; | 3 | import java.util.HashSet; |
5 | -import java.util.List; | ||
6 | import java.util.Set; | 4 | import java.util.Set; |
7 | 5 | ||
6 | +import org.junit.Before; | ||
8 | import org.junit.Test; | 7 | import org.junit.Test; |
9 | import org.onlab.onos.net.Link; | 8 | import org.onlab.onos.net.Link; |
10 | import org.onlab.onos.net.flow.TrafficSelector; | 9 | import org.onlab.onos.net.flow.TrafficSelector; |
11 | import org.onlab.onos.net.flow.TrafficTreatment; | 10 | import org.onlab.onos.net.flow.TrafficTreatment; |
12 | -import org.onlab.onos.net.flow.criteria.Criterion; | ||
13 | -import org.onlab.onos.net.flow.instructions.Instruction; | ||
14 | 11 | ||
12 | +import static org.hamcrest.CoreMatchers.not; | ||
15 | import static org.hamcrest.MatcherAssert.assertThat; | 13 | import static org.hamcrest.MatcherAssert.assertThat; |
14 | +import static org.hamcrest.Matchers.equalTo; | ||
16 | import static org.hamcrest.Matchers.is; | 15 | import static org.hamcrest.Matchers.is; |
16 | +import static org.onlab.onos.net.NetTestTools.link; | ||
17 | 17 | ||
18 | +/** | ||
19 | + * Unit tests for the LinkCollectionIntent class. | ||
20 | + */ | ||
18 | public class TestLinkCollectionIntent { | 21 | public class TestLinkCollectionIntent { |
19 | 22 | ||
20 | - private static class MockSelector implements TrafficSelector { | 23 | + private Link link1 = link("dev1", 1, "dev2", 2); |
21 | - @Override | 24 | + private Link link2 = link("dev1", 1, "dev3", 2); |
22 | - public Set<Criterion> criteria() { | 25 | + private Link link3 = link("dev2", 1, "dev3", 2); |
23 | - return new HashSet<Criterion>(); | 26 | + |
24 | - } | 27 | + private Set<Link> links1; |
28 | + private Set<Link> links2; | ||
29 | + | ||
30 | + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); | ||
31 | + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); | ||
32 | + | ||
33 | + private LinkCollectionIntent makeLinkCollection(long id, Set<Link> links) { | ||
34 | + return new LinkCollectionIntent(new IntentId(id), | ||
35 | + selector, treatment, links); | ||
36 | + } | ||
37 | + | ||
38 | + @Before | ||
39 | + public void setup() { | ||
40 | + links1 = new HashSet<>(); | ||
41 | + links2 = new HashSet<>(); | ||
25 | } | 42 | } |
26 | 43 | ||
27 | - private static class MockTreatment implements TrafficTreatment { | 44 | + /** |
28 | - @Override | 45 | + * Tests the equals() method where two LinkCollectionIntents have references |
29 | - public List<Instruction> instructions() { | 46 | + * to the same Links in different orders. These should compare equal. |
30 | - return new ArrayList<>(); | 47 | + */ |
31 | - } | 48 | + @Test |
49 | + public void testSameEquals() { | ||
50 | + links1.add(link1); | ||
51 | + links1.add(link2); | ||
52 | + links1.add(link3); | ||
53 | + | ||
54 | + links2.add(link3); | ||
55 | + links2.add(link2); | ||
56 | + links2.add(link1); | ||
57 | + | ||
58 | + LinkCollectionIntent i1 = makeLinkCollection(12, links1); | ||
59 | + LinkCollectionIntent i2 = makeLinkCollection(12, links2); | ||
60 | + | ||
61 | + assertThat(i1, is(equalTo(i2))); | ||
32 | } | 62 | } |
33 | 63 | ||
64 | + /** | ||
65 | + * Tests the equals() method where two LinkCollectionIntents have references | ||
66 | + * to different Links. These should compare not equal. | ||
67 | + */ | ||
34 | @Test | 68 | @Test |
35 | - public void testComparison() { | 69 | + public void testLinksDifferentEquals() { |
36 | - TrafficSelector selector = new MockSelector(); | 70 | + links1.add(link1); |
37 | - TrafficTreatment treatment = new MockTreatment(); | 71 | + links1.add(link2); |
38 | - Set<Link> links = new HashSet<>(); | 72 | + |
39 | - LinkCollectionIntent i1 = new LinkCollectionIntent(new IntentId(12), | 73 | + links2.add(link3); |
40 | - selector, treatment, links); | 74 | + links2.add(link1); |
41 | - LinkCollectionIntent i2 = new LinkCollectionIntent(new IntentId(12), | ||
42 | - selector, treatment, links); | ||
43 | 75 | ||
44 | - assertThat(i1.equals(i2), is(true)); | 76 | + LinkCollectionIntent i1 = makeLinkCollection(12, links1); |
77 | + LinkCollectionIntent i2 = makeLinkCollection(12, links2); | ||
78 | + | ||
79 | + assertThat(i1, is(not(equalTo(i2)))); | ||
45 | } | 80 | } |
46 | 81 | ||
82 | + /** | ||
83 | + * Tests the equals() method where two LinkCollectionIntents have different | ||
84 | + * ids. These should compare not equal. | ||
85 | + */ | ||
86 | + @Test | ||
87 | + public void testBaseDifferentEquals() { | ||
88 | + links1.add(link1); | ||
89 | + links1.add(link2); | ||
90 | + | ||
91 | + links2.add(link2); | ||
92 | + links2.add(link1); | ||
93 | + | ||
94 | + LinkCollectionIntent i1 = makeLinkCollection(1, links1); | ||
95 | + LinkCollectionIntent i2 = makeLinkCollection(2, links2); | ||
96 | + | ||
97 | + assertThat(i1, is(not(equalTo(i2)))); | ||
98 | + } | ||
99 | + | ||
100 | + /** | ||
101 | + * Tests that the hashCode() values for two equivalent LinkCollectionIntent | ||
102 | + * objects are the same. | ||
103 | + */ | ||
104 | + @Test | ||
105 | + public void testHashCodeEquals() { | ||
106 | + links1.add(link1); | ||
107 | + links1.add(link2); | ||
108 | + links1.add(link3); | ||
109 | + | ||
110 | + links2.add(link3); | ||
111 | + links2.add(link2); | ||
112 | + links2.add(link1); | ||
113 | + | ||
114 | + LinkCollectionIntent i1 = makeLinkCollection(1, links1); | ||
115 | + LinkCollectionIntent i2 = makeLinkCollection(1, links2); | ||
116 | + | ||
117 | + assertThat(i1.hashCode(), is(equalTo(i2.hashCode()))); | ||
118 | + } | ||
119 | + | ||
120 | + /** | ||
121 | + * Tests that the hashCode() values for two distinct LinkCollectionIntent | ||
122 | + * objects are different. | ||
123 | + */ | ||
124 | + @Test | ||
125 | + public void testHashCodeDifferent() { | ||
126 | + links1.add(link1); | ||
127 | + links1.add(link2); | ||
128 | + | ||
129 | + links2.add(link1); | ||
130 | + links2.add(link3); | ||
131 | + | ||
132 | + LinkCollectionIntent i1 = makeLinkCollection(1, links1); | ||
133 | + LinkCollectionIntent i2 = makeLinkCollection(1, links2); | ||
134 | + | ||
135 | + assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode())))); | ||
136 | + } | ||
137 | + | ||
138 | + /** | ||
139 | + * Checks that the HostToHostIntent class is immutable. | ||
140 | + */ | ||
141 | + @Test | ||
142 | + public void checkImmutability() { | ||
143 | + ImmutableClassChecker.assertThatClassIsImmutable(LinkCollectionIntent.class); | ||
144 | + } | ||
47 | } | 145 | } | ... | ... |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.Set; | ||
5 | + | ||
6 | +import org.junit.Before; | ||
7 | +import org.junit.Test; | ||
8 | +import org.onlab.onos.net.ConnectPoint; | ||
9 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
10 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
11 | + | ||
12 | +import static org.hamcrest.CoreMatchers.not; | ||
13 | +import static org.hamcrest.MatcherAssert.assertThat; | ||
14 | +import static org.hamcrest.Matchers.equalTo; | ||
15 | +import static org.hamcrest.Matchers.is; | ||
16 | +import static org.onlab.onos.net.NetTestTools.connectPoint; | ||
17 | + | ||
18 | +/** | ||
19 | + * Unit tests for the MultiPointToSinglePointIntent class. | ||
20 | + */ | ||
21 | +public class TestMultiPointToSinglePointIntent { | ||
22 | + | ||
23 | + private ConnectPoint point1 = connectPoint("dev1", 1); | ||
24 | + private ConnectPoint point2 = connectPoint("dev2", 1); | ||
25 | + private ConnectPoint point3 = connectPoint("dev3", 1); | ||
26 | + | ||
27 | + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); | ||
28 | + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); | ||
29 | + | ||
30 | + Set<ConnectPoint> ingress1; | ||
31 | + Set<ConnectPoint> ingress2; | ||
32 | + | ||
33 | + /** | ||
34 | + * Creates a MultiPointToSinglePointIntent object. | ||
35 | + * | ||
36 | + * @param id identifier to use for the new intent | ||
37 | + * @param ingress set of ingress points | ||
38 | + * @param egress egress point | ||
39 | + * @return MultiPointToSinglePoint intent | ||
40 | + */ | ||
41 | + private MultiPointToSinglePointIntent makeIntent(long id, | ||
42 | + Set<ConnectPoint> ingress, | ||
43 | + ConnectPoint egress) { | ||
44 | + return new MultiPointToSinglePointIntent(new IntentId(id), | ||
45 | + selector, | ||
46 | + treatment, | ||
47 | + ingress, | ||
48 | + egress); | ||
49 | + } | ||
50 | + | ||
51 | + /** | ||
52 | + * Initializes the ingress sets. | ||
53 | + */ | ||
54 | + @Before | ||
55 | + public void setup() { | ||
56 | + ingress1 = new HashSet<>(); | ||
57 | + ingress2 = new HashSet<>(); | ||
58 | + } | ||
59 | + | ||
60 | + /** | ||
61 | + * Tests the equals() method where two MultiPointToSinglePoint have references | ||
62 | + * to the same Links in different orders. These should compare equal. | ||
63 | + */ | ||
64 | + @Test | ||
65 | + public void testSameEquals() { | ||
66 | + | ||
67 | + Set<ConnectPoint> ingress1 = new HashSet<>(); | ||
68 | + ingress1.add(point2); | ||
69 | + ingress1.add(point3); | ||
70 | + | ||
71 | + Set<ConnectPoint> ingress2 = new HashSet<>(); | ||
72 | + ingress2.add(point3); | ||
73 | + ingress2.add(point2); | ||
74 | + | ||
75 | + Intent i1 = makeIntent(12, ingress1, point1); | ||
76 | + Intent i2 = makeIntent(12, ingress2, point1); | ||
77 | + | ||
78 | + assertThat(i1, is(equalTo(i2))); | ||
79 | + } | ||
80 | + | ||
81 | + /** | ||
82 | + * Tests the equals() method where two MultiPointToSinglePoint have references | ||
83 | + * to different Links. These should compare not equal. | ||
84 | + */ | ||
85 | + @Test | ||
86 | + public void testLinksDifferentEquals() { | ||
87 | + ingress1.add(point3); | ||
88 | + | ||
89 | + ingress2.add(point3); | ||
90 | + ingress2.add(point2); | ||
91 | + | ||
92 | + Intent i1 = makeIntent(12, ingress1, point1); | ||
93 | + Intent i2 = makeIntent(12, ingress2, point1); | ||
94 | + | ||
95 | + assertThat(i1, is(not(equalTo(i2)))); | ||
96 | + } | ||
97 | + | ||
98 | + /** | ||
99 | + * Tests the equals() method where two MultiPointToSinglePoint have different | ||
100 | + * ids. These should compare not equal. | ||
101 | + */ | ||
102 | + @Test | ||
103 | + public void testBaseDifferentEquals() { | ||
104 | + ingress1.add(point3); | ||
105 | + ingress2.add(point3); | ||
106 | + | ||
107 | + Intent i1 = makeIntent(12, ingress1, point1); | ||
108 | + Intent i2 = makeIntent(11, ingress2, point1); | ||
109 | + | ||
110 | + assertThat(i1, is(not(equalTo(i2)))); | ||
111 | + } | ||
112 | + | ||
113 | + /** | ||
114 | + * Tests that the hashCode() values for two equivalent MultiPointToSinglePoint | ||
115 | + * objects are the same. | ||
116 | + */ | ||
117 | + @Test | ||
118 | + public void testHashCodeEquals() { | ||
119 | + ingress1.add(point2); | ||
120 | + ingress1.add(point3); | ||
121 | + | ||
122 | + ingress2.add(point3); | ||
123 | + ingress2.add(point2); | ||
124 | + | ||
125 | + Intent i1 = makeIntent(12, ingress1, point1); | ||
126 | + Intent i2 = makeIntent(12, ingress2, point1); | ||
127 | + | ||
128 | + assertThat(i1.hashCode(), is(equalTo(i2.hashCode()))); | ||
129 | + } | ||
130 | + | ||
131 | + /** | ||
132 | + * Tests that the hashCode() values for two distinct MultiPointToSinglePoint | ||
133 | + * objects are different. | ||
134 | + */ | ||
135 | + @Test | ||
136 | + public void testHashCodeDifferent() { | ||
137 | + ingress1.add(point2); | ||
138 | + | ||
139 | + ingress2.add(point3); | ||
140 | + ingress2.add(point2); | ||
141 | + | ||
142 | + Intent i1 = makeIntent(12, ingress1, point1); | ||
143 | + Intent i2 = makeIntent(12, ingress2, point1); | ||
144 | + | ||
145 | + | ||
146 | + assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode())))); | ||
147 | + } | ||
148 | + | ||
149 | + /** | ||
150 | + * Checks that the MultiPointToSinglePointIntent class is immutable. | ||
151 | + */ | ||
152 | + @Test | ||
153 | + public void checkImmutability() { | ||
154 | + ImmutableClassChecker. | ||
155 | + assertThatClassIsImmutable(MultiPointToSinglePointIntent.class); | ||
156 | + } | ||
157 | +} |
1 | +package org.onlab.onos.net.intent.impl; | ||
2 | + | ||
3 | +import java.util.List; | ||
4 | + | ||
5 | +import org.hamcrest.Matchers; | ||
6 | +import org.junit.Before; | ||
7 | +import org.junit.Test; | ||
8 | +import org.onlab.onos.net.Host; | ||
9 | +import org.onlab.onos.net.HostId; | ||
10 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
11 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
12 | +import org.onlab.onos.net.host.HostService; | ||
13 | +import org.onlab.onos.net.intent.HostToHostIntent; | ||
14 | +import org.onlab.onos.net.intent.Intent; | ||
15 | +import org.onlab.onos.net.intent.IntentId; | ||
16 | +import org.onlab.onos.net.intent.IntentTestsMocks; | ||
17 | +import org.onlab.onos.net.intent.PathIntent; | ||
18 | +import org.onlab.packet.MacAddress; | ||
19 | +import org.onlab.packet.VlanId; | ||
20 | + | ||
21 | +import static org.easymock.EasyMock.createMock; | ||
22 | +import static org.easymock.EasyMock.eq; | ||
23 | +import static org.easymock.EasyMock.expect; | ||
24 | +import static org.easymock.EasyMock.replay; | ||
25 | +import static org.hamcrest.CoreMatchers.notNullValue; | ||
26 | +import static org.hamcrest.MatcherAssert.assertThat; | ||
27 | +import static org.hamcrest.Matchers.hasSize; | ||
28 | +import static org.hamcrest.Matchers.is; | ||
29 | +import static org.onlab.onos.net.NetTestTools.hid; | ||
30 | +import static org.onlab.onos.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath; | ||
31 | + | ||
32 | +/** | ||
33 | + * Unit tests for the HostToHost intent compiler. | ||
34 | + */ | ||
35 | +public class TestHostToHostIntentCompiler { | ||
36 | + private static final String HOST_ONE_MAC = "00:00:00:00:00:01"; | ||
37 | + private static final String HOST_TWO_MAC = "00:00:00:00:00:02"; | ||
38 | + private static final String HOST_ONE_VLAN = "-1"; | ||
39 | + private static final String HOST_TWO_VLAN = "-1"; | ||
40 | + private static final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN; | ||
41 | + private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN; | ||
42 | + | ||
43 | + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); | ||
44 | + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); | ||
45 | + | ||
46 | + private HostId hostOneId = HostId.hostId(HOST_ONE); | ||
47 | + private HostId hostTwoId = HostId.hostId(HOST_TWO); | ||
48 | + private HostService mockHostService; | ||
49 | + | ||
50 | + @Before | ||
51 | + public void setup() { | ||
52 | + Host hostOne = createMock(Host.class); | ||
53 | + expect(hostOne.mac()).andReturn(new MacAddress(HOST_ONE_MAC.getBytes())).anyTimes(); | ||
54 | + expect(hostOne.vlan()).andReturn(VlanId.vlanId()).anyTimes(); | ||
55 | + replay(hostOne); | ||
56 | + | ||
57 | + Host hostTwo = createMock(Host.class); | ||
58 | + expect(hostTwo.mac()).andReturn(new MacAddress(HOST_TWO_MAC.getBytes())).anyTimes(); | ||
59 | + expect(hostTwo.vlan()).andReturn(VlanId.vlanId()).anyTimes(); | ||
60 | + replay(hostTwo); | ||
61 | + | ||
62 | + mockHostService = createMock(HostService.class); | ||
63 | + expect(mockHostService.getHost(eq(hostOneId))).andReturn(hostOne).anyTimes(); | ||
64 | + expect(mockHostService.getHost(eq(hostTwoId))).andReturn(hostTwo).anyTimes(); | ||
65 | + replay(mockHostService); | ||
66 | + } | ||
67 | + | ||
68 | + /** | ||
69 | + * Creates a HostToHost intent based on two host Ids. | ||
70 | + * | ||
71 | + * @param oneIdString string for host one id | ||
72 | + * @param twoIdString string for host two id | ||
73 | + * @return HostToHostIntent for the two hosts | ||
74 | + */ | ||
75 | + private HostToHostIntent makeIntent(String oneIdString, String twoIdString) { | ||
76 | + return new HostToHostIntent(new IntentId(12), | ||
77 | + hid(oneIdString), | ||
78 | + hid(twoIdString), | ||
79 | + selector, | ||
80 | + treatment); | ||
81 | + } | ||
82 | + | ||
83 | + /** | ||
84 | + * Creates a compiler for HostToHost intents. | ||
85 | + * | ||
86 | + * @param hops string array describing the path hops to use when compiling | ||
87 | + * @return HostToHost intent compiler | ||
88 | + */ | ||
89 | + private HostToHostIntentCompiler makeCompiler(String[] hops) { | ||
90 | + HostToHostIntentCompiler compiler = | ||
91 | + new HostToHostIntentCompiler(); | ||
92 | + compiler.pathService = new IntentTestsMocks.MockPathService(hops); | ||
93 | + compiler.hostService = mockHostService; | ||
94 | + IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator(); | ||
95 | + compiler.intentIdGenerator = | ||
96 | + new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator); | ||
97 | + return compiler; | ||
98 | + } | ||
99 | + | ||
100 | + | ||
101 | + /** | ||
102 | + * Tests a pair of hosts with 8 hops between them. | ||
103 | + */ | ||
104 | + @Test | ||
105 | + public void testSingleLongPathCompilation() { | ||
106 | + | ||
107 | + HostToHostIntent intent = makeIntent(HOST_ONE, | ||
108 | + HOST_TWO); | ||
109 | + assertThat(intent, is(notNullValue())); | ||
110 | + | ||
111 | + String[] hops = {HOST_ONE, "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", HOST_TWO}; | ||
112 | + HostToHostIntentCompiler compiler = makeCompiler(hops); | ||
113 | + assertThat(compiler, is(notNullValue())); | ||
114 | + | ||
115 | + List<Intent> result = compiler.compile(intent); | ||
116 | + assertThat(result, is(Matchers.notNullValue())); | ||
117 | + assertThat(result, hasSize(2)); | ||
118 | + Intent forwardResultIntent = result.get(0); | ||
119 | + assertThat(forwardResultIntent instanceof PathIntent, is(true)); | ||
120 | + Intent reverseResultIntent = result.get(1); | ||
121 | + assertThat(reverseResultIntent instanceof PathIntent, is(true)); | ||
122 | + | ||
123 | + if (forwardResultIntent instanceof PathIntent) { | ||
124 | + PathIntent forwardPathIntent = (PathIntent) forwardResultIntent; | ||
125 | + assertThat(forwardPathIntent.path().links(), hasSize(9)); | ||
126 | + assertThat(forwardPathIntent.path().links(), linksHasPath(HOST_ONE, "h1")); | ||
127 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h1", "h2")); | ||
128 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h2", "h3")); | ||
129 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h3", "h4")); | ||
130 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h4", "h5")); | ||
131 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h5", "h6")); | ||
132 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h6", "h7")); | ||
133 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h7", "h8")); | ||
134 | + assertThat(forwardPathIntent.path().links(), linksHasPath("h8", HOST_TWO)); | ||
135 | + } | ||
136 | + | ||
137 | + if (reverseResultIntent instanceof PathIntent) { | ||
138 | + PathIntent reversePathIntent = (PathIntent) reverseResultIntent; | ||
139 | + assertThat(reversePathIntent.path().links(), hasSize(9)); | ||
140 | + assertThat(reversePathIntent.path().links(), linksHasPath("h1", HOST_ONE)); | ||
141 | + assertThat(reversePathIntent.path().links(), linksHasPath("h2", "h1")); | ||
142 | + assertThat(reversePathIntent.path().links(), linksHasPath("h3", "h2")); | ||
143 | + assertThat(reversePathIntent.path().links(), linksHasPath("h4", "h3")); | ||
144 | + assertThat(reversePathIntent.path().links(), linksHasPath("h5", "h4")); | ||
145 | + assertThat(reversePathIntent.path().links(), linksHasPath("h6", "h5")); | ||
146 | + assertThat(reversePathIntent.path().links(), linksHasPath("h7", "h6")); | ||
147 | + assertThat(reversePathIntent.path().links(), linksHasPath("h8", "h7")); | ||
148 | + assertThat(reversePathIntent.path().links(), linksHasPath(HOST_TWO, "h8")); | ||
149 | + } | ||
150 | + } | ||
151 | +} |
core/net/src/test/java/org/onlab/onos/net/intent/impl/TestMultiPointToSinglePointIntentCompiler.java
0 → 100644
1 | +package org.onlab.onos.net.intent.impl; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.List; | ||
5 | +import java.util.Set; | ||
6 | + | ||
7 | +import org.hamcrest.Matchers; | ||
8 | +import org.junit.Test; | ||
9 | +import org.onlab.onos.net.ConnectPoint; | ||
10 | +import org.onlab.onos.net.ElementId; | ||
11 | +import org.onlab.onos.net.Path; | ||
12 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
13 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
14 | +import org.onlab.onos.net.intent.Intent; | ||
15 | +import org.onlab.onos.net.intent.IntentId; | ||
16 | +import org.onlab.onos.net.intent.IntentTestsMocks; | ||
17 | +import org.onlab.onos.net.intent.LinkCollectionIntent; | ||
18 | +import org.onlab.onos.net.intent.MultiPointToSinglePointIntent; | ||
19 | +import org.onlab.onos.net.topology.LinkWeight; | ||
20 | +import org.onlab.onos.net.topology.PathService; | ||
21 | + | ||
22 | +import static org.hamcrest.CoreMatchers.notNullValue; | ||
23 | +import static org.hamcrest.MatcherAssert.assertThat; | ||
24 | +import static org.hamcrest.Matchers.hasSize; | ||
25 | +import static org.hamcrest.Matchers.is; | ||
26 | +import static org.onlab.onos.net.NetTestTools.connectPoint; | ||
27 | +import static org.onlab.onos.net.NetTestTools.createPath; | ||
28 | +import static org.onlab.onos.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath; | ||
29 | + | ||
30 | +/** | ||
31 | + * Unit tests for the MultiPointToSinglePoint intent compiler. | ||
32 | + */ | ||
33 | +public class TestMultiPointToSinglePointIntentCompiler { | ||
34 | + | ||
35 | + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); | ||
36 | + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); | ||
37 | + | ||
38 | + /** | ||
39 | + * Mock path service for creating paths within the test. | ||
40 | + */ | ||
41 | + private static class MockPathService implements PathService { | ||
42 | + | ||
43 | + final String[] pathHops; | ||
44 | + | ||
45 | + /** | ||
46 | + * Constructor that provides a set of hops to mock. | ||
47 | + * | ||
48 | + * @param pathHops path hops to mock | ||
49 | + */ | ||
50 | + MockPathService(String[] pathHops) { | ||
51 | + this.pathHops = pathHops; | ||
52 | + } | ||
53 | + | ||
54 | + @Override | ||
55 | + public Set<Path> getPaths(ElementId src, ElementId dst) { | ||
56 | + Set<Path> result = new HashSet<>(); | ||
57 | + | ||
58 | + String[] allHops = new String[pathHops.length + 1]; | ||
59 | + allHops[0] = src.toString(); | ||
60 | + System.arraycopy(pathHops, 0, allHops, 1, pathHops.length); | ||
61 | + | ||
62 | + result.add(createPath(allHops)); | ||
63 | + return result; | ||
64 | + } | ||
65 | + | ||
66 | + @Override | ||
67 | + public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) { | ||
68 | + return null; | ||
69 | + } | ||
70 | + } | ||
71 | + | ||
72 | + /** | ||
73 | + * Creates a MultiPointToSinglePoint intent for a group of ingress points | ||
74 | + * and an egress point. | ||
75 | + * | ||
76 | + * @param ingressIds array of ingress device ids | ||
77 | + * @param egressId device id of the egress point | ||
78 | + * @return MultiPointToSinglePoint intent | ||
79 | + */ | ||
80 | + private MultiPointToSinglePointIntent makeIntent(String[] ingressIds, String egressId) { | ||
81 | + Set<ConnectPoint> ingressPoints = new HashSet<>(); | ||
82 | + ConnectPoint egressPoint = connectPoint(egressId, 1); | ||
83 | + | ||
84 | + for (String ingressId : ingressIds) { | ||
85 | + ingressPoints.add(connectPoint(ingressId, 1)); | ||
86 | + } | ||
87 | + | ||
88 | + return new MultiPointToSinglePointIntent( | ||
89 | + new IntentId(12), | ||
90 | + selector, | ||
91 | + treatment, | ||
92 | + ingressPoints, | ||
93 | + egressPoint); | ||
94 | + } | ||
95 | + | ||
96 | + /** | ||
97 | + * Creates a compiler for MultiPointToSinglePoint intents. | ||
98 | + * | ||
99 | + * @param hops hops to use while computing paths for this intent | ||
100 | + * @return MultiPointToSinglePoint intent | ||
101 | + */ | ||
102 | + private MultiPointToSinglePointIntentCompiler makeCompiler(String[] hops) { | ||
103 | + MultiPointToSinglePointIntentCompiler compiler = | ||
104 | + new MultiPointToSinglePointIntentCompiler(); | ||
105 | + compiler.pathService = new MockPathService(hops); | ||
106 | + IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator(); | ||
107 | + compiler.intentIdGenerator = | ||
108 | + new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator); | ||
109 | + return compiler; | ||
110 | + } | ||
111 | + | ||
112 | + /** | ||
113 | + * Tests a single ingress point with 8 hops to its egress point. | ||
114 | + */ | ||
115 | + @Test | ||
116 | + public void testSingleLongPathCompilation() { | ||
117 | + | ||
118 | + String[] ingress = {"ingress"}; | ||
119 | + String egress = "egress"; | ||
120 | + | ||
121 | + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); | ||
122 | + assertThat(intent, is(notNullValue())); | ||
123 | + | ||
124 | + String[] hops = {"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", | ||
125 | + egress}; | ||
126 | + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); | ||
127 | + assertThat(compiler, is(notNullValue())); | ||
128 | + | ||
129 | + List<Intent> result = compiler.compile(intent); | ||
130 | + assertThat(result, is(Matchers.notNullValue())); | ||
131 | + assertThat(result, hasSize(1)); | ||
132 | + Intent resultIntent = result.get(0); | ||
133 | + assertThat(resultIntent instanceof LinkCollectionIntent, is(true)); | ||
134 | + | ||
135 | + if (resultIntent instanceof LinkCollectionIntent) { | ||
136 | + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; | ||
137 | + assertThat(linkIntent.links(), hasSize(9)); | ||
138 | + assertThat(linkIntent.links(), linksHasPath("ingress", "h1")); | ||
139 | + assertThat(linkIntent.links(), linksHasPath("h1", "h2")); | ||
140 | + assertThat(linkIntent.links(), linksHasPath("h2", "h3")); | ||
141 | + assertThat(linkIntent.links(), linksHasPath("h4", "h5")); | ||
142 | + assertThat(linkIntent.links(), linksHasPath("h5", "h6")); | ||
143 | + assertThat(linkIntent.links(), linksHasPath("h7", "h8")); | ||
144 | + assertThat(linkIntent.links(), linksHasPath("h8", "egress")); | ||
145 | + } | ||
146 | + } | ||
147 | + | ||
148 | + /** | ||
149 | + * Tests a simple topology where two ingress points share some path segments | ||
150 | + * and some path segments are not shared. | ||
151 | + */ | ||
152 | + @Test | ||
153 | + public void testTwoIngressCompilation() { | ||
154 | + String[] ingress = {"ingress1", "ingress2"}; | ||
155 | + String egress = "egress"; | ||
156 | + | ||
157 | + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); | ||
158 | + assertThat(intent, is(notNullValue())); | ||
159 | + | ||
160 | + final String[] hops = {"inner1", "inner2", egress}; | ||
161 | + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); | ||
162 | + assertThat(compiler, is(notNullValue())); | ||
163 | + | ||
164 | + List<Intent> result = compiler.compile(intent); | ||
165 | + assertThat(result, is(notNullValue())); | ||
166 | + assertThat(result, hasSize(1)); | ||
167 | + Intent resultIntent = result.get(0); | ||
168 | + assertThat(resultIntent instanceof LinkCollectionIntent, is(true)); | ||
169 | + | ||
170 | + if (resultIntent instanceof LinkCollectionIntent) { | ||
171 | + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; | ||
172 | + assertThat(linkIntent.links(), hasSize(4)); | ||
173 | + assertThat(linkIntent.links(), linksHasPath("ingress1", "inner1")); | ||
174 | + assertThat(linkIntent.links(), linksHasPath("ingress2", "inner1")); | ||
175 | + assertThat(linkIntent.links(), linksHasPath("inner1", "inner2")); | ||
176 | + assertThat(linkIntent.links(), linksHasPath("inner2", "egress")); | ||
177 | + } | ||
178 | + } | ||
179 | + | ||
180 | + /** | ||
181 | + * Tests a large number of ingress points that share a common path to the | ||
182 | + * egress point. | ||
183 | + */ | ||
184 | + @Test | ||
185 | + public void testMultiIngressCompilation() { | ||
186 | + String[] ingress = {"i1", "i2", "i3", "i4", "i5", | ||
187 | + "i6", "i7", "i8", "i9", "i10"}; | ||
188 | + String egress = "e"; | ||
189 | + | ||
190 | + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); | ||
191 | + assertThat(intent, is(notNullValue())); | ||
192 | + | ||
193 | + final String[] hops = {"n1", egress}; | ||
194 | + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); | ||
195 | + assertThat(compiler, is(notNullValue())); | ||
196 | + | ||
197 | + List<Intent> result = compiler.compile(intent); | ||
198 | + assertThat(result, is(notNullValue())); | ||
199 | + assertThat(result, hasSize(1)); | ||
200 | + Intent resultIntent = result.get(0); | ||
201 | + assertThat(resultIntent instanceof LinkCollectionIntent, is(true)); | ||
202 | + | ||
203 | + if (resultIntent instanceof LinkCollectionIntent) { | ||
204 | + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; | ||
205 | + assertThat(linkIntent.links(), hasSize(ingress.length + 1)); | ||
206 | + for (String ingressToCheck : ingress) { | ||
207 | + assertThat(linkIntent.links(), | ||
208 | + linksHasPath(ingressToCheck, | ||
209 | + "n1")); | ||
210 | + } | ||
211 | + assertThat(linkIntent.links(), linksHasPath("n1", egress)); | ||
212 | + } | ||
213 | + } | ||
214 | +} |
... | @@ -19,21 +19,10 @@ | ... | @@ -19,21 +19,10 @@ |
19 | <dependencies> | 19 | <dependencies> |
20 | <dependency> | 20 | <dependency> |
21 | <groupId>org.onlab.onos</groupId> | 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-serializers</artifactId> | 22 | <artifactId>onos-core-serializers</artifactId> |
27 | <version>${project.version}</version> | 23 | <version>${project.version}</version> |
28 | </dependency> | 24 | </dependency> |
29 | 25 | ||
30 | - | ||
31 | - <dependency> | ||
32 | - <groupId>org.onlab.onos</groupId> | ||
33 | - <artifactId>onlab-nio</artifactId> | ||
34 | - <version>${project.version}</version> | ||
35 | - </dependency> | ||
36 | - | ||
37 | <dependency> | 26 | <dependency> |
38 | <groupId>org.onlab.onos</groupId> | 27 | <groupId>org.onlab.onos</groupId> |
39 | <artifactId>onlab-netty</artifactId> | 28 | <artifactId>onlab-netty</artifactId> |
... | @@ -50,10 +39,6 @@ | ... | @@ -50,10 +39,6 @@ |
50 | </dependency> | 39 | </dependency> |
51 | 40 | ||
52 | <dependency> | 41 | <dependency> |
53 | - <groupId>org.apache.felix</groupId> | ||
54 | - <artifactId>org.apache.felix.scr.annotations</artifactId> | ||
55 | - </dependency> | ||
56 | - <dependency> | ||
57 | <groupId>com.google.guava</groupId> | 42 | <groupId>com.google.guava</groupId> |
58 | <artifactId>guava-testlib</artifactId> | 43 | <artifactId>guava-testlib</artifactId> |
59 | <scope>test</scope> | 44 | <scope>test</scope> |
... | @@ -69,13 +54,4 @@ | ... | @@ -69,13 +54,4 @@ |
69 | </dependency> | 54 | </dependency> |
70 | </dependencies> | 55 | </dependencies> |
71 | 56 | ||
72 | - <build> | ||
73 | - <plugins> | ||
74 | - <plugin> | ||
75 | - <groupId>org.apache.felix</groupId> | ||
76 | - <artifactId>maven-scr-plugin</artifactId> | ||
77 | - </plugin> | ||
78 | - </plugins> | ||
79 | - </build> | ||
80 | - | ||
81 | </project> | 57 | </project> | ... | ... |
... | @@ -114,7 +114,7 @@ public class ClusterCommunicationManager | ... | @@ -114,7 +114,7 @@ public class ClusterCommunicationManager |
114 | message.subject().value(), SERIALIZER.encode(message)); | 114 | message.subject().value(), SERIALIZER.encode(message)); |
115 | return true; | 115 | return true; |
116 | } catch (IOException e) { | 116 | } catch (IOException e) { |
117 | - log.error("Failed to send cluster message to nodeId: " + toNodeId, e); | 117 | + log.trace("Failed to send cluster message to nodeId: " + toNodeId, e); |
118 | throw e; | 118 | throw e; |
119 | } | 119 | } |
120 | } | 120 | } | ... | ... |
... | @@ -15,7 +15,7 @@ import org.onlab.onos.net.device.DefaultPortDescription; | ... | @@ -15,7 +15,7 @@ import org.onlab.onos.net.device.DefaultPortDescription; |
15 | import org.onlab.onos.net.device.DeviceDescription; | 15 | import org.onlab.onos.net.device.DeviceDescription; |
16 | import org.onlab.onos.net.device.PortDescription; | 16 | import org.onlab.onos.net.device.PortDescription; |
17 | import org.onlab.onos.store.Timestamp; | 17 | import org.onlab.onos.store.Timestamp; |
18 | -import org.onlab.onos.store.common.impl.Timestamped; | 18 | +import org.onlab.onos.store.impl.Timestamped; |
19 | 19 | ||
20 | /* | 20 | /* |
21 | * Collection of Description of a Device and Ports, given from a Provider. | 21 | * Collection of Description of a Device and Ports, given from a Provider. | ... | ... |
... | @@ -38,7 +38,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; | ... | @@ -38,7 +38,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; |
38 | import org.onlab.onos.store.cluster.messaging.ClusterMessage; | 38 | import org.onlab.onos.store.cluster.messaging.ClusterMessage; |
39 | import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler; | 39 | import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler; |
40 | import org.onlab.onos.store.cluster.messaging.MessageSubject; | 40 | import org.onlab.onos.store.cluster.messaging.MessageSubject; |
41 | -import org.onlab.onos.store.common.impl.Timestamped; | 41 | +import org.onlab.onos.store.impl.Timestamped; |
42 | import org.onlab.onos.store.serializers.KryoSerializer; | 42 | import org.onlab.onos.store.serializers.KryoSerializer; |
43 | import org.onlab.onos.store.serializers.DistributedStoreSerializers; | 43 | import org.onlab.onos.store.serializers.DistributedStoreSerializers; |
44 | import org.onlab.packet.ChassisId; | 44 | import org.onlab.packet.ChassisId; |
... | @@ -516,12 +516,12 @@ public class GossipDeviceStore | ... | @@ -516,12 +516,12 @@ public class GossipDeviceStore |
516 | Map<PortNumber, Port> ports, | 516 | Map<PortNumber, Port> ports, |
517 | Set<PortNumber> processed) { | 517 | Set<PortNumber> processed) { |
518 | List<DeviceEvent> events = new ArrayList<>(); | 518 | List<DeviceEvent> events = new ArrayList<>(); |
519 | - Iterator<PortNumber> iterator = ports.keySet().iterator(); | 519 | + Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator(); |
520 | while (iterator.hasNext()) { | 520 | while (iterator.hasNext()) { |
521 | - PortNumber portNumber = iterator.next(); | 521 | + Entry<PortNumber, Port> e = iterator.next(); |
522 | + PortNumber portNumber = e.getKey(); | ||
522 | if (!processed.contains(portNumber)) { | 523 | if (!processed.contains(portNumber)) { |
523 | - events.add(new DeviceEvent(PORT_REMOVED, device, | 524 | + events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue())); |
524 | - ports.get(portNumber))); | ||
525 | iterator.remove(); | 525 | iterator.remove(); |
526 | } | 526 | } |
527 | } | 527 | } |
... | @@ -1139,7 +1139,7 @@ public class GossipDeviceStore | ... | @@ -1139,7 +1139,7 @@ public class GossipDeviceStore |
1139 | try { | 1139 | try { |
1140 | unicastMessage(peer, DEVICE_ADVERTISE, ad); | 1140 | unicastMessage(peer, DEVICE_ADVERTISE, ad); |
1141 | } catch (IOException e) { | 1141 | } catch (IOException e) { |
1142 | - log.error("Failed to send anti-entropy advertisement", e); | 1142 | + log.debug("Failed to send anti-entropy advertisement to {}", peer); |
1143 | return; | 1143 | return; |
1144 | } | 1144 | } |
1145 | } catch (Exception e) { | 1145 | } catch (Exception e) { | ... | ... |
core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InitDeviceDescs.java
deleted
100644 → 0
1 | -package org.onlab.onos.store.device.impl; | ||
2 | - | ||
3 | -import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | - | ||
5 | -import org.apache.commons.lang3.concurrent.ConcurrentException; | ||
6 | -import org.apache.commons.lang3.concurrent.ConcurrentInitializer; | ||
7 | -import org.onlab.onos.net.device.DeviceDescription; | ||
8 | -import org.onlab.onos.store.common.impl.Timestamped; | ||
9 | - | ||
10 | -// FIXME: consider removing this class | ||
11 | -public final class InitDeviceDescs | ||
12 | - implements ConcurrentInitializer<DeviceDescriptions> { | ||
13 | - | ||
14 | - private final Timestamped<DeviceDescription> deviceDesc; | ||
15 | - | ||
16 | - public InitDeviceDescs(Timestamped<DeviceDescription> deviceDesc) { | ||
17 | - this.deviceDesc = checkNotNull(deviceDesc); | ||
18 | - } | ||
19 | - @Override | ||
20 | - public DeviceDescriptions get() throws ConcurrentException { | ||
21 | - return new DeviceDescriptions(deviceDesc); | ||
22 | - } | ||
23 | -} |
... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; | ... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; |
3 | import org.onlab.onos.net.DeviceId; | 3 | import org.onlab.onos.net.DeviceId; |
4 | import org.onlab.onos.net.device.DeviceDescription; | 4 | import org.onlab.onos.net.device.DeviceDescription; |
5 | import org.onlab.onos.net.provider.ProviderId; | 5 | import org.onlab.onos.net.provider.ProviderId; |
6 | -import org.onlab.onos.store.common.impl.Timestamped; | 6 | +import org.onlab.onos.store.impl.Timestamped; |
7 | 7 | ||
8 | import com.google.common.base.MoreObjects; | 8 | import com.google.common.base.MoreObjects; |
9 | 9 | ... | ... |
... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; | ... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; |
3 | import org.onlab.onos.net.DeviceId; | 3 | import org.onlab.onos.net.DeviceId; |
4 | import org.onlab.onos.net.device.DeviceDescription; | 4 | import org.onlab.onos.net.device.DeviceDescription; |
5 | import org.onlab.onos.net.provider.ProviderId; | 5 | import org.onlab.onos.net.provider.ProviderId; |
6 | -import org.onlab.onos.store.common.impl.Timestamped; | 6 | +import org.onlab.onos.store.impl.Timestamped; |
7 | 7 | ||
8 | import com.esotericsoftware.kryo.Kryo; | 8 | import com.esotericsoftware.kryo.Kryo; |
9 | import com.esotericsoftware.kryo.Serializer; | 9 | import com.esotericsoftware.kryo.Serializer; | ... | ... |
... | @@ -5,7 +5,7 @@ import java.util.List; | ... | @@ -5,7 +5,7 @@ import java.util.List; |
5 | import org.onlab.onos.net.DeviceId; | 5 | import org.onlab.onos.net.DeviceId; |
6 | import org.onlab.onos.net.device.PortDescription; | 6 | import org.onlab.onos.net.device.PortDescription; |
7 | import org.onlab.onos.net.provider.ProviderId; | 7 | import org.onlab.onos.net.provider.ProviderId; |
8 | -import org.onlab.onos.store.common.impl.Timestamped; | 8 | +import org.onlab.onos.store.impl.Timestamped; |
9 | 9 | ||
10 | import com.google.common.base.MoreObjects; | 10 | import com.google.common.base.MoreObjects; |
11 | 11 | ... | ... |
... | @@ -5,7 +5,7 @@ import java.util.List; | ... | @@ -5,7 +5,7 @@ import java.util.List; |
5 | import org.onlab.onos.net.DeviceId; | 5 | import org.onlab.onos.net.DeviceId; |
6 | import org.onlab.onos.net.device.PortDescription; | 6 | import org.onlab.onos.net.device.PortDescription; |
7 | import org.onlab.onos.net.provider.ProviderId; | 7 | import org.onlab.onos.net.provider.ProviderId; |
8 | -import org.onlab.onos.store.common.impl.Timestamped; | 8 | +import org.onlab.onos.store.impl.Timestamped; |
9 | 9 | ||
10 | import com.esotericsoftware.kryo.Kryo; | 10 | import com.esotericsoftware.kryo.Kryo; |
11 | import com.esotericsoftware.kryo.Serializer; | 11 | import com.esotericsoftware.kryo.Serializer; | ... | ... |
... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; | ... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; |
3 | import org.onlab.onos.net.DeviceId; | 3 | import org.onlab.onos.net.DeviceId; |
4 | import org.onlab.onos.net.device.PortDescription; | 4 | import org.onlab.onos.net.device.PortDescription; |
5 | import org.onlab.onos.net.provider.ProviderId; | 5 | import org.onlab.onos.net.provider.ProviderId; |
6 | -import org.onlab.onos.store.common.impl.Timestamped; | 6 | +import org.onlab.onos.store.impl.Timestamped; |
7 | 7 | ||
8 | import com.google.common.base.MoreObjects; | 8 | import com.google.common.base.MoreObjects; |
9 | 9 | ... | ... |
... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; | ... | @@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl; |
3 | import org.onlab.onos.net.DeviceId; | 3 | import org.onlab.onos.net.DeviceId; |
4 | import org.onlab.onos.net.device.PortDescription; | 4 | import org.onlab.onos.net.device.PortDescription; |
5 | import org.onlab.onos.net.provider.ProviderId; | 5 | import org.onlab.onos.net.provider.ProviderId; |
6 | -import org.onlab.onos.store.common.impl.Timestamped; | 6 | +import org.onlab.onos.store.impl.Timestamped; |
7 | 7 | ||
8 | import com.esotericsoftware.kryo.Kryo; | 8 | import com.esotericsoftware.kryo.Kryo; |
9 | import com.esotericsoftware.kryo.Serializer; | 9 | import com.esotericsoftware.kryo.Serializer; | ... | ... |
1 | +package org.onlab.onos.store.flow; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | + | ||
5 | +import java.util.Collection; | ||
6 | +import java.util.Collections; | ||
7 | + | ||
8 | +import org.onlab.onos.cluster.NodeId; | ||
9 | + | ||
10 | +import com.google.common.base.Optional; | ||
11 | + | ||
12 | +/** | ||
13 | + * Class to represent placement information about Master/Backup copy. | ||
14 | + */ | ||
15 | +public final class ReplicaInfo { | ||
16 | + | ||
17 | + private final Optional<NodeId> master; | ||
18 | + private final Collection<NodeId> backups; | ||
19 | + | ||
20 | + /** | ||
21 | + * Creates a ReplicaInfo instance. | ||
22 | + * | ||
23 | + * @param master NodeId of the node where the master copy should be | ||
24 | + * @param backups collection of NodeId, where backup copies should be placed | ||
25 | + */ | ||
26 | + public ReplicaInfo(NodeId master, Collection<NodeId> backups) { | ||
27 | + this.master = Optional.fromNullable(master); | ||
28 | + this.backups = checkNotNull(backups); | ||
29 | + } | ||
30 | + | ||
31 | + /** | ||
32 | + * Returns the NodeId, if there is a Node where the master copy should be. | ||
33 | + * | ||
34 | + * @return NodeId, where the master copy should be placed | ||
35 | + */ | ||
36 | + public Optional<NodeId> master() { | ||
37 | + return master; | ||
38 | + } | ||
39 | + | ||
40 | + /** | ||
41 | + * Returns the collection of NodeId, where backup copies should be placed. | ||
42 | + * | ||
43 | + * @return collection of NodeId, where backup copies should be placed | ||
44 | + */ | ||
45 | + public Collection<NodeId> backups() { | ||
46 | + return backups; | ||
47 | + } | ||
48 | + | ||
49 | + // for Serializer | ||
50 | + private ReplicaInfo() { | ||
51 | + this.master = Optional.absent(); | ||
52 | + this.backups = Collections.emptyList(); | ||
53 | + } | ||
54 | +} |
1 | +package org.onlab.onos.store.flow; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | + | ||
5 | +import org.onlab.onos.event.AbstractEvent; | ||
6 | +import org.onlab.onos.net.DeviceId; | ||
7 | + | ||
8 | +/** | ||
9 | + * Describes a device replicainfo event. | ||
10 | + */ | ||
11 | +public class ReplicaInfoEvent extends AbstractEvent<ReplicaInfoEvent.Type, DeviceId> { | ||
12 | + | ||
13 | + private final ReplicaInfo replicaInfo; | ||
14 | + | ||
15 | + /** | ||
16 | + * Types of Replica info event. | ||
17 | + */ | ||
18 | + public enum Type { | ||
19 | + /** | ||
20 | + * Event to notify that master placement should be changed. | ||
21 | + */ | ||
22 | + MASTER_CHANGED, | ||
23 | + // | ||
24 | + // BACKUPS_CHANGED? | ||
25 | + } | ||
26 | + | ||
27 | + | ||
28 | + /** | ||
29 | + * Creates an event of a given type and for the specified device, | ||
30 | + * and replica info. | ||
31 | + * | ||
32 | + * @param type replicainfo event type | ||
33 | + * @param device event device subject | ||
34 | + * @param replicaInfo replicainfo | ||
35 | + */ | ||
36 | + public ReplicaInfoEvent(Type type, DeviceId device, ReplicaInfo replicaInfo) { | ||
37 | + super(type, device); | ||
38 | + this.replicaInfo = checkNotNull(replicaInfo); | ||
39 | + } | ||
40 | + | ||
41 | + /** | ||
42 | + * Returns the current replica information for the subject. | ||
43 | + * | ||
44 | + * @return replica information for the subject | ||
45 | + */ | ||
46 | + public ReplicaInfo replicaInfo() { | ||
47 | + return replicaInfo; | ||
48 | + }; | ||
49 | +} |
1 | +package org.onlab.onos.store.flow; | ||
2 | + | ||
3 | +import org.onlab.onos.net.DeviceId; | ||
4 | + | ||
5 | +/** | ||
6 | + * Service to return where the Replica should be placed. | ||
7 | + */ | ||
8 | +public interface ReplicaInfoService { | ||
9 | + | ||
10 | + // returns where it should be. | ||
11 | + /** | ||
12 | + * Returns the placement information for given Device. | ||
13 | + * | ||
14 | + * @param deviceId identifier of the device | ||
15 | + * @return placement information | ||
16 | + */ | ||
17 | + ReplicaInfo getReplicaInfoFor(DeviceId deviceId); | ||
18 | +} |
1 | +package org.onlab.onos.store.flow.impl; | ||
2 | + | ||
3 | +import static org.slf4j.LoggerFactory.getLogger; | ||
4 | +import static org.onlab.onos.store.flow.ReplicaInfoEvent.Type.MASTER_CHANGED; | ||
5 | + | ||
6 | +import java.util.Collections; | ||
7 | +import java.util.List; | ||
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.Reference; | ||
13 | +import org.apache.felix.scr.annotations.ReferenceCardinality; | ||
14 | +import org.apache.felix.scr.annotations.Service; | ||
15 | +import org.onlab.onos.cluster.NodeId; | ||
16 | +import org.onlab.onos.event.AbstractListenerRegistry; | ||
17 | +import org.onlab.onos.event.EventDeliveryService; | ||
18 | +import org.onlab.onos.mastership.MastershipEvent; | ||
19 | +import org.onlab.onos.mastership.MastershipListener; | ||
20 | +import org.onlab.onos.mastership.MastershipService; | ||
21 | +import org.onlab.onos.net.DeviceId; | ||
22 | +import org.onlab.onos.store.flow.ReplicaInfo; | ||
23 | +import org.onlab.onos.store.flow.ReplicaInfoEvent; | ||
24 | +import org.onlab.onos.store.flow.ReplicaInfoEventListener; | ||
25 | +import org.onlab.onos.store.flow.ReplicaInfoService; | ||
26 | +import org.slf4j.Logger; | ||
27 | + | ||
28 | +/** | ||
29 | + * Manages replica placement information. | ||
30 | + */ | ||
31 | +@Component(immediate = true) | ||
32 | +@Service | ||
33 | +public class ReplicaInfoManager implements ReplicaInfoService { | ||
34 | + | ||
35 | + private final Logger log = getLogger(getClass()); | ||
36 | + | ||
37 | + private final MastershipListener mastershipListener = new InternalMastershipListener(); | ||
38 | + | ||
39 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
40 | + protected EventDeliveryService eventDispatcher; | ||
41 | + | ||
42 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
43 | + protected MastershipService mastershipService; | ||
44 | + | ||
45 | + protected final AbstractListenerRegistry<ReplicaInfoEvent, ReplicaInfoEventListener> | ||
46 | + listenerRegistry = new AbstractListenerRegistry<>(); | ||
47 | + | ||
48 | + @Activate | ||
49 | + public void activate() { | ||
50 | + eventDispatcher.addSink(ReplicaInfoEvent.class, listenerRegistry); | ||
51 | + mastershipService.addListener(mastershipListener); | ||
52 | + log.info("Started"); | ||
53 | + } | ||
54 | + | ||
55 | + @Deactivate | ||
56 | + public void deactivate() { | ||
57 | + eventDispatcher.removeSink(ReplicaInfoEvent.class); | ||
58 | + mastershipService.removeListener(mastershipListener); | ||
59 | + log.info("Stopped"); | ||
60 | + } | ||
61 | + | ||
62 | + @Override | ||
63 | + public ReplicaInfo getReplicaInfoFor(DeviceId deviceId) { | ||
64 | + // TODO: populate backup List when we reach the point we need them. | ||
65 | + return new ReplicaInfo(mastershipService.getMasterFor(deviceId), | ||
66 | + Collections.<NodeId>emptyList()); | ||
67 | + } | ||
68 | + | ||
69 | + final class InternalMastershipListener implements MastershipListener { | ||
70 | + | ||
71 | + @Override | ||
72 | + public void event(MastershipEvent event) { | ||
73 | + // TODO: distinguish stby list update, when MastershipService, | ||
74 | + // start publishing them | ||
75 | + final List<NodeId> standbyList = Collections.<NodeId>emptyList(); | ||
76 | + eventDispatcher.post(new ReplicaInfoEvent(MASTER_CHANGED, | ||
77 | + event.subject(), | ||
78 | + new ReplicaInfo(event.master(), standbyList))); | ||
79 | + } | ||
80 | + } | ||
81 | + | ||
82 | +} |
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; |
... | @@ -32,7 +38,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; | ... | @@ -32,7 +38,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; |
32 | import org.onlab.onos.store.cluster.messaging.ClusterMessage; | 38 | import org.onlab.onos.store.cluster.messaging.ClusterMessage; |
33 | import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler; | 39 | import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler; |
34 | import org.onlab.onos.store.cluster.messaging.MessageSubject; | 40 | import org.onlab.onos.store.cluster.messaging.MessageSubject; |
35 | -import org.onlab.onos.store.common.impl.Timestamped; | 41 | +import org.onlab.onos.store.impl.Timestamped; |
36 | import org.onlab.onos.store.serializers.DistributedStoreSerializers; | 42 | import org.onlab.onos.store.serializers.DistributedStoreSerializers; |
37 | import org.onlab.onos.store.serializers.KryoSerializer; | 43 | import org.onlab.onos.store.serializers.KryoSerializer; |
38 | import org.onlab.packet.IpPrefix; | 44 | import org.onlab.packet.IpPrefix; |
... | @@ -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 to {}", peer); | ||
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.common.impl; | 1 | +package org.onlab.onos.store.impl; |
2 | 2 | ||
3 | import static com.google.common.base.Preconditions.checkNotNull; | 3 | import static com.google.common.base.Preconditions.checkNotNull; |
4 | 4 | ||
... | @@ -58,12 +58,12 @@ public final class Timestamped<T> { | ... | @@ -58,12 +58,12 @@ public final class Timestamped<T> { |
58 | } | 58 | } |
59 | 59 | ||
60 | /** | 60 | /** |
61 | - * Tests if this timestamp is newer thatn the specified timestamp. | 61 | + * Tests if this timestamp is newer than the specified timestamp. |
62 | - * @param timestamp to compare agains | 62 | + * @param other timestamp to compare against |
63 | * @return true if this instance is newer | 63 | * @return true if this instance is newer |
64 | */ | 64 | */ |
65 | - public boolean isNewer(Timestamp timestamp) { | 65 | + public boolean isNewer(Timestamp other) { |
66 | - return this.timestamp.compareTo(checkNotNull(timestamp)) > 0; | 66 | + return this.timestamp.compareTo(checkNotNull(other)) > 0; |
67 | } | 67 | } |
68 | 68 | ||
69 | @Override | 69 | @Override | ... | ... |
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; |
... | @@ -41,7 +39,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; | ... | @@ -41,7 +39,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; |
41 | import org.onlab.onos.store.cluster.messaging.ClusterMessage; | 39 | import org.onlab.onos.store.cluster.messaging.ClusterMessage; |
42 | import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler; | 40 | import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler; |
43 | import org.onlab.onos.store.cluster.messaging.MessageSubject; | 41 | import org.onlab.onos.store.cluster.messaging.MessageSubject; |
44 | -import org.onlab.onos.store.common.impl.Timestamped; | 42 | +import org.onlab.onos.store.impl.Timestamped; |
45 | import org.onlab.onos.store.serializers.DistributedStoreSerializers; | 43 | import org.onlab.onos.store.serializers.DistributedStoreSerializers; |
46 | import org.onlab.onos.store.serializers.KryoSerializer; | 44 | import org.onlab.onos.store.serializers.KryoSerializer; |
47 | import org.onlab.util.KryoPool; | 45 | import org.onlab.util.KryoPool; |
... | @@ -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 | } | ... | ... |
... | @@ -4,7 +4,7 @@ import com.google.common.base.MoreObjects; | ... | @@ -4,7 +4,7 @@ import com.google.common.base.MoreObjects; |
4 | 4 | ||
5 | import org.onlab.onos.net.link.LinkDescription; | 5 | import org.onlab.onos.net.link.LinkDescription; |
6 | import org.onlab.onos.net.provider.ProviderId; | 6 | import org.onlab.onos.net.provider.ProviderId; |
7 | -import org.onlab.onos.store.common.impl.Timestamped; | 7 | +import org.onlab.onos.store.impl.Timestamped; |
8 | 8 | ||
9 | /** | 9 | /** |
10 | * Information published by GossipDeviceStore to notify peers of a device | 10 | * Information published by GossipDeviceStore to notify peers of a device | ... | ... |
1 | package org.onlab.onos.store.serializers; | 1 | package org.onlab.onos.store.serializers; |
2 | 2 | ||
3 | -import org.onlab.onos.store.common.impl.Timestamped; | ||
4 | import org.onlab.onos.store.impl.MastershipBasedTimestamp; | 3 | import org.onlab.onos.store.impl.MastershipBasedTimestamp; |
4 | +import org.onlab.onos.store.impl.Timestamped; | ||
5 | import org.onlab.onos.store.impl.WallClockTimestamp; | 5 | import org.onlab.onos.store.impl.WallClockTimestamp; |
6 | import org.onlab.util.KryoPool; | 6 | import org.onlab.util.KryoPool; |
7 | 7 | ... | ... |
1 | -package org.onlab.onos.store.common.impl; | 1 | +package org.onlab.onos.store.impl; |
2 | 2 | ||
3 | import static org.junit.Assert.*; | 3 | import static org.junit.Assert.*; |
4 | 4 | ||
... | @@ -6,7 +6,6 @@ import java.nio.ByteBuffer; | ... | @@ -6,7 +6,6 @@ import java.nio.ByteBuffer; |
6 | 6 | ||
7 | import org.junit.Test; | 7 | import org.junit.Test; |
8 | import org.onlab.onos.store.Timestamp; | 8 | import org.onlab.onos.store.Timestamp; |
9 | -import org.onlab.onos.store.impl.MastershipBasedTimestamp; | ||
10 | import org.onlab.util.KryoPool; | 9 | import org.onlab.util.KryoPool; |
11 | 10 | ||
12 | import com.google.common.testing.EqualsTester; | 11 | import com.google.common.testing.EqualsTester; | ... | ... |
... | @@ -19,10 +19,6 @@ | ... | @@ -19,10 +19,6 @@ |
19 | <dependencies> | 19 | <dependencies> |
20 | <dependency> | 20 | <dependency> |
21 | <groupId>org.onlab.onos</groupId> | 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-serializers</artifactId> | 22 | <artifactId>onos-core-serializers</artifactId> |
27 | <version>${project.version}</version> | 23 | <version>${project.version}</version> |
28 | </dependency> | 24 | </dependency> |
... | @@ -38,23 +34,6 @@ | ... | @@ -38,23 +34,6 @@ |
38 | <scope>test</scope> | 34 | <scope>test</scope> |
39 | <version>${project.version}</version> | 35 | <version>${project.version}</version> |
40 | </dependency> | 36 | </dependency> |
41 | - <dependency> | ||
42 | - <groupId>org.apache.felix</groupId> | ||
43 | - <artifactId>org.apache.felix.scr.annotations</artifactId> | ||
44 | - </dependency> | ||
45 | - <dependency> | ||
46 | - <groupId>com.hazelcast</groupId> | ||
47 | - <artifactId>hazelcast</artifactId> | ||
48 | - </dependency> | ||
49 | </dependencies> | 37 | </dependencies> |
50 | 38 | ||
51 | - <build> | ||
52 | - <plugins> | ||
53 | - <plugin> | ||
54 | - <groupId>org.apache.felix</groupId> | ||
55 | - <artifactId>maven-scr-plugin</artifactId> | ||
56 | - </plugin> | ||
57 | - </plugins> | ||
58 | - </build> | ||
59 | - | ||
60 | </project> | 39 | </project> | ... | ... |
... | @@ -19,34 +19,13 @@ | ... | @@ -19,34 +19,13 @@ |
19 | <dependencies> | 19 | <dependencies> |
20 | <dependency> | 20 | <dependency> |
21 | <groupId>org.onlab.onos</groupId> | 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-serializers</artifactId> | 22 | <artifactId>onos-core-serializers</artifactId> |
27 | <version>${project.version}</version> | 23 | <version>${project.version}</version> |
28 | </dependency> | 24 | </dependency> |
29 | <dependency> | 25 | <dependency> |
30 | - <groupId>org.apache.felix</groupId> | ||
31 | - <artifactId>org.apache.felix.scr.annotations</artifactId> | ||
32 | - </dependency> | ||
33 | - <dependency> | ||
34 | - <groupId>com.hazelcast</groupId> | ||
35 | - <artifactId>hazelcast</artifactId> | ||
36 | - </dependency> | ||
37 | - <dependency> | ||
38 | <groupId>org.apache.commons</groupId> | 26 | <groupId>org.apache.commons</groupId> |
39 | <artifactId>commons-lang3</artifactId> | 27 | <artifactId>commons-lang3</artifactId> |
40 | </dependency> | 28 | </dependency> |
41 | </dependencies> | 29 | </dependencies> |
42 | 30 | ||
43 | - <build> | ||
44 | - <plugins> | ||
45 | - <plugin> | ||
46 | - <groupId>org.apache.felix</groupId> | ||
47 | - <artifactId>maven-scr-plugin</artifactId> | ||
48 | - </plugin> | ||
49 | - </plugins> | ||
50 | - </build> | ||
51 | - | ||
52 | </project> | 31 | </project> | ... | ... |
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 | -} |
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 | -} |
... | @@ -17,35 +17,13 @@ | ... | @@ -17,35 +17,13 @@ |
17 | <modules> | 17 | <modules> |
18 | <module>common</module> | 18 | <module>common</module> |
19 | <module>cluster</module> | 19 | <module>cluster</module> |
20 | - <module>net</module> | ||
21 | </modules> | 20 | </modules> |
22 | 21 | ||
23 | <dependencies> | 22 | <dependencies> |
24 | <dependency> | 23 | <dependency> |
25 | - <groupId>com.google.guava</groupId> | ||
26 | - <artifactId>guava</artifactId> | ||
27 | - </dependency> | ||
28 | - <dependency> | ||
29 | - <groupId>org.onlab.onos</groupId> | ||
30 | - <artifactId>onlab-misc</artifactId> | ||
31 | - </dependency> | ||
32 | - <dependency> | ||
33 | - <groupId>org.onlab.onos</groupId> | ||
34 | - <artifactId>onlab-junit</artifactId> | ||
35 | - </dependency> | ||
36 | - <dependency> | ||
37 | <groupId>com.hazelcast</groupId> | 24 | <groupId>com.hazelcast</groupId> |
38 | <artifactId>hazelcast</artifactId> | 25 | <artifactId>hazelcast</artifactId> |
39 | </dependency> | 26 | </dependency> |
40 | </dependencies> | 27 | </dependencies> |
41 | 28 | ||
42 | - <build> | ||
43 | - <plugins> | ||
44 | - <plugin> | ||
45 | - <groupId>org.apache.felix</groupId> | ||
46 | - <artifactId>maven-bundle-plugin</artifactId> | ||
47 | - </plugin> | ||
48 | - </plugins> | ||
49 | - </build> | ||
50 | - | ||
51 | </project> | 29 | </project> | ... | ... |
1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | 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"> | ||
3 | <modelVersion>4.0.0</modelVersion> | 5 | <modelVersion>4.0.0</modelVersion> |
4 | 6 | ||
5 | <parent> | 7 | <parent> |
... | @@ -19,24 +21,17 @@ | ... | @@ -19,24 +21,17 @@ |
19 | <module>dist</module> | 21 | <module>dist</module> |
20 | <module>hz</module> | 22 | <module>hz</module> |
21 | <module>serializers</module> | 23 | <module>serializers</module> |
22 | - </modules> | 24 | + </modules> |
23 | 25 | ||
24 | <dependencies> | 26 | <dependencies> |
25 | <dependency> | 27 | <dependency> |
26 | - <groupId>com.google.guava</groupId> | ||
27 | - <artifactId>guava</artifactId> | ||
28 | - </dependency> | ||
29 | - <dependency> | ||
30 | - <groupId>org.onlab.onos</groupId> | ||
31 | - <artifactId>onlab-misc</artifactId> | ||
32 | - </dependency> | ||
33 | - <dependency> | ||
34 | <groupId>org.onlab.onos</groupId> | 28 | <groupId>org.onlab.onos</groupId> |
35 | - <artifactId>onlab-junit</artifactId> | 29 | + <artifactId>onos-api</artifactId> |
36 | </dependency> | 30 | </dependency> |
31 | + | ||
37 | <dependency> | 32 | <dependency> |
38 | - <groupId>com.hazelcast</groupId> | 33 | + <groupId>org.apache.felix</groupId> |
39 | - <artifactId>hazelcast</artifactId> | 34 | + <artifactId>org.apache.felix.scr.annotations</artifactId> |
40 | </dependency> | 35 | </dependency> |
41 | </dependencies> | 36 | </dependencies> |
42 | 37 | ||
... | @@ -44,7 +39,7 @@ | ... | @@ -44,7 +39,7 @@ |
44 | <plugins> | 39 | <plugins> |
45 | <plugin> | 40 | <plugin> |
46 | <groupId>org.apache.felix</groupId> | 41 | <groupId>org.apache.felix</groupId> |
47 | - <artifactId>maven-bundle-plugin</artifactId> | 42 | + <artifactId>maven-scr-plugin</artifactId> |
48 | </plugin> | 43 | </plugin> |
49 | </plugins> | 44 | </plugins> |
50 | </build> | 45 | </build> | ... | ... |
... | @@ -18,14 +18,6 @@ | ... | @@ -18,14 +18,6 @@ |
18 | 18 | ||
19 | <dependencies> | 19 | <dependencies> |
20 | <dependency> | 20 | <dependency> |
21 | - <groupId>org.onlab.onos</groupId> | ||
22 | - <artifactId>onos-api</artifactId> | ||
23 | - </dependency> | ||
24 | - <dependency> | ||
25 | - <groupId>org.apache.felix</groupId> | ||
26 | - <artifactId>org.apache.felix.scr.annotations</artifactId> | ||
27 | - </dependency> | ||
28 | - <dependency> | ||
29 | <groupId>com.esotericsoftware</groupId> | 21 | <groupId>com.esotericsoftware</groupId> |
30 | <artifactId>kryo</artifactId> | 22 | <artifactId>kryo</artifactId> |
31 | </dependency> | 23 | </dependency> |
... | @@ -36,13 +28,4 @@ | ... | @@ -36,13 +28,4 @@ |
36 | </dependency> | 28 | </dependency> |
37 | </dependencies> | 29 | </dependencies> |
38 | 30 | ||
39 | - <build> | ||
40 | - <plugins> | ||
41 | - <plugin> | ||
42 | - <groupId>org.apache.felix</groupId> | ||
43 | - <artifactId>maven-scr-plugin</artifactId> | ||
44 | - </plugin> | ||
45 | - </plugins> | ||
46 | - </build> | ||
47 | - | ||
48 | </project> | 31 | </project> | ... | ... |
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,6 +17,8 @@ import org.onlab.onos.net.DefaultPort; | ... | @@ -17,6 +17,8 @@ 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.MastershipRole; | 24 | import org.onlab.onos.net.MastershipRole; |
... | @@ -24,16 +26,21 @@ import org.onlab.onos.net.Port; | ... | @@ -24,16 +26,21 @@ import org.onlab.onos.net.Port; |
24 | import org.onlab.onos.net.PortNumber; | 26 | import org.onlab.onos.net.PortNumber; |
25 | import org.onlab.onos.net.device.DefaultDeviceDescription; | 27 | import org.onlab.onos.net.device.DefaultDeviceDescription; |
26 | import org.onlab.onos.net.device.DefaultPortDescription; | 28 | import org.onlab.onos.net.device.DefaultPortDescription; |
29 | +import org.onlab.onos.net.host.DefaultHostDescription; | ||
30 | +import org.onlab.onos.net.host.HostDescription; | ||
27 | import org.onlab.onos.net.link.DefaultLinkDescription; | 31 | import org.onlab.onos.net.link.DefaultLinkDescription; |
28 | import org.onlab.onos.net.provider.ProviderId; | 32 | import org.onlab.onos.net.provider.ProviderId; |
29 | import org.onlab.onos.store.Timestamp; | 33 | import org.onlab.onos.store.Timestamp; |
30 | import org.onlab.packet.ChassisId; | 34 | import org.onlab.packet.ChassisId; |
31 | import org.onlab.packet.IpAddress; | 35 | import org.onlab.packet.IpAddress; |
32 | import org.onlab.packet.IpPrefix; | 36 | import org.onlab.packet.IpPrefix; |
37 | +import org.onlab.packet.MacAddress; | ||
38 | +import org.onlab.packet.VlanId; | ||
33 | import org.onlab.util.KryoPool; | 39 | import org.onlab.util.KryoPool; |
34 | 40 | ||
35 | import com.google.common.collect.ImmutableList; | 41 | import com.google.common.collect.ImmutableList; |
36 | import com.google.common.collect.ImmutableMap; | 42 | import com.google.common.collect.ImmutableMap; |
43 | +import com.google.common.collect.ImmutableSet; | ||
37 | 44 | ||
38 | public final class KryoPoolUtil { | 45 | public final class KryoPoolUtil { |
39 | 46 | ||
... | @@ -43,6 +50,8 @@ public final class KryoPoolUtil { | ... | @@ -43,6 +50,8 @@ public final class KryoPoolUtil { |
43 | public static final KryoPool MISC = KryoPool.newBuilder() | 50 | public static final KryoPool MISC = KryoPool.newBuilder() |
44 | .register(IpPrefix.class, new IpPrefixSerializer()) | 51 | .register(IpPrefix.class, new IpPrefixSerializer()) |
45 | .register(IpAddress.class, new IpAddressSerializer()) | 52 | .register(IpAddress.class, new IpAddressSerializer()) |
53 | + .register(MacAddress.class, new MacAddressSerializer()) | ||
54 | + .register(VlanId.class) | ||
46 | .build(); | 55 | .build(); |
47 | 56 | ||
48 | // TODO: Populate other classes | 57 | // TODO: Populate other classes |
... | @@ -53,6 +62,7 @@ public final class KryoPoolUtil { | ... | @@ -53,6 +62,7 @@ public final class KryoPoolUtil { |
53 | .register(MISC) | 62 | .register(MISC) |
54 | .register(ImmutableMap.class, new ImmutableMapSerializer()) | 63 | .register(ImmutableMap.class, new ImmutableMapSerializer()) |
55 | .register(ImmutableList.class, new ImmutableListSerializer()) | 64 | .register(ImmutableList.class, new ImmutableListSerializer()) |
65 | + .register(ImmutableSet.class, new ImmutableSetSerializer()) | ||
56 | .register( | 66 | .register( |
57 | // | 67 | // |
58 | ArrayList.class, | 68 | ArrayList.class, |
... | @@ -73,8 +83,10 @@ public final class KryoPoolUtil { | ... | @@ -73,8 +83,10 @@ public final class KryoPoolUtil { |
73 | DefaultPortDescription.class, | 83 | DefaultPortDescription.class, |
74 | Element.class, | 84 | Element.class, |
75 | Link.Type.class, | 85 | Link.Type.class, |
76 | - Timestamp.class | 86 | + Timestamp.class, |
77 | - | 87 | + HostId.class, |
88 | + HostDescription.class, | ||
89 | + DefaultHostDescription.class | ||
78 | ) | 90 | ) |
79 | .register(URI.class, new URISerializer()) | 91 | .register(URI.class, new URISerializer()) |
80 | .register(NodeId.class, new NodeIdSerializer()) | 92 | .register(NodeId.class, new NodeIdSerializer()) |
... | @@ -87,6 +99,7 @@ public final class KryoPoolUtil { | ... | @@ -87,6 +99,7 @@ public final class KryoPoolUtil { |
87 | .register(DefaultLink.class, new DefaultLinkSerializer()) | 99 | .register(DefaultLink.class, new DefaultLinkSerializer()) |
88 | .register(MastershipTerm.class, new MastershipTermSerializer()) | 100 | .register(MastershipTerm.class, new MastershipTermSerializer()) |
89 | .register(MastershipRole.class, new MastershipRoleSerializer()) | 101 | .register(MastershipRole.class, new MastershipRoleSerializer()) |
102 | + .register(HostLocation.class, new HostLocationSerializer()) | ||
90 | 103 | ||
91 | .build(); | 104 | .build(); |
92 | 105 | ... | ... |
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 | +} |
... | @@ -18,26 +18,9 @@ | ... | @@ -18,26 +18,9 @@ |
18 | 18 | ||
19 | <dependencies> | 19 | <dependencies> |
20 | <dependency> | 20 | <dependency> |
21 | - <groupId>org.onlab.onos</groupId> | ||
22 | - <artifactId>onos-api</artifactId> | ||
23 | - </dependency> | ||
24 | - <dependency> | ||
25 | - <groupId>org.apache.felix</groupId> | ||
26 | - <artifactId>org.apache.felix.scr.annotations</artifactId> | ||
27 | - </dependency> | ||
28 | - <dependency> | ||
29 | <groupId>org.apache.commons</groupId> | 21 | <groupId>org.apache.commons</groupId> |
30 | <artifactId>commons-lang3</artifactId> | 22 | <artifactId>commons-lang3</artifactId> |
31 | </dependency> | 23 | </dependency> |
32 | </dependencies> | 24 | </dependencies> |
33 | 25 | ||
34 | - <build> | ||
35 | - <plugins> | ||
36 | - <plugin> | ||
37 | - <groupId>org.apache.felix</groupId> | ||
38 | - <artifactId>maven-scr-plugin</artifactId> | ||
39 | - </plugin> | ||
40 | - </plugins> | ||
41 | - </build> | ||
42 | - | ||
43 | </project> | 26 | </project> | ... | ... |
... | @@ -5,8 +5,6 @@ import com.google.common.collect.ImmutableList; | ... | @@ -5,8 +5,6 @@ import com.google.common.collect.ImmutableList; |
5 | import com.google.common.collect.Maps; | 5 | import com.google.common.collect.Maps; |
6 | import com.google.common.collect.Sets; | 6 | import com.google.common.collect.Sets; |
7 | 7 | ||
8 | -import org.apache.commons.lang3.concurrent.ConcurrentException; | ||
9 | -import org.apache.commons.lang3.concurrent.ConcurrentInitializer; | ||
10 | import org.apache.felix.scr.annotations.Activate; | 8 | import org.apache.felix.scr.annotations.Activate; |
11 | import org.apache.felix.scr.annotations.Component; | 9 | import org.apache.felix.scr.annotations.Component; |
12 | import org.apache.felix.scr.annotations.Deactivate; | 10 | import org.apache.felix.scr.annotations.Deactivate; |
... | @@ -36,6 +34,7 @@ import org.slf4j.Logger; | ... | @@ -36,6 +34,7 @@ import org.slf4j.Logger; |
36 | 34 | ||
37 | import java.util.ArrayList; | 35 | import java.util.ArrayList; |
38 | import java.util.Collections; | 36 | import java.util.Collections; |
37 | +import java.util.HashMap; | ||
39 | import java.util.HashSet; | 38 | import java.util.HashSet; |
40 | import java.util.Iterator; | 39 | import java.util.Iterator; |
41 | import java.util.List; | 40 | import java.util.List; |
... | @@ -72,8 +71,7 @@ public class SimpleDeviceStore | ... | @@ -72,8 +71,7 @@ public class SimpleDeviceStore |
72 | public static final String DEVICE_NOT_FOUND = "Device with ID %s not found"; | 71 | public static final String DEVICE_NOT_FOUND = "Device with ID %s not found"; |
73 | 72 | ||
74 | // collection of Description given from various providers | 73 | // collection of Description given from various providers |
75 | - private final ConcurrentMap<DeviceId, | 74 | + private final ConcurrentMap<DeviceId, Map<ProviderId, DeviceDescriptions>> |
76 | - ConcurrentMap<ProviderId, DeviceDescriptions>> | ||
77 | deviceDescs = Maps.newConcurrentMap(); | 75 | deviceDescs = Maps.newConcurrentMap(); |
78 | 76 | ||
79 | // cache of Device and Ports generated by compositing descriptions from providers | 77 | // cache of Device and Ports generated by compositing descriptions from providers |
... | @@ -118,15 +116,16 @@ public class SimpleDeviceStore | ... | @@ -118,15 +116,16 @@ public class SimpleDeviceStore |
118 | DeviceId deviceId, | 116 | DeviceId deviceId, |
119 | DeviceDescription deviceDescription) { | 117 | DeviceDescription deviceDescription) { |
120 | 118 | ||
121 | - ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs | 119 | + Map<ProviderId, DeviceDescriptions> providerDescs |
122 | - = getDeviceDescriptions(deviceId); | 120 | + = getOrCreateDeviceDescriptions(deviceId); |
123 | 121 | ||
124 | synchronized (providerDescs) { | 122 | synchronized (providerDescs) { |
125 | // locking per device | 123 | // locking per device |
126 | 124 | ||
127 | DeviceDescriptions descs | 125 | DeviceDescriptions descs |
128 | - = createIfAbsentUnchecked(providerDescs, providerId, | 126 | + = getOrCreateProviderDeviceDescriptions(providerDescs, |
129 | - new InitDeviceDescs(deviceDescription)); | 127 | + providerId, |
128 | + deviceDescription); | ||
130 | 129 | ||
131 | Device oldDevice = devices.get(deviceId); | 130 | Device oldDevice = devices.get(deviceId); |
132 | // update description | 131 | // update description |
... | @@ -193,8 +192,8 @@ public class SimpleDeviceStore | ... | @@ -193,8 +192,8 @@ public class SimpleDeviceStore |
193 | 192 | ||
194 | @Override | 193 | @Override |
195 | public DeviceEvent markOffline(DeviceId deviceId) { | 194 | public DeviceEvent markOffline(DeviceId deviceId) { |
196 | - ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs | 195 | + Map<ProviderId, DeviceDescriptions> providerDescs |
197 | - = getDeviceDescriptions(deviceId); | 196 | + = getOrCreateDeviceDescriptions(deviceId); |
198 | 197 | ||
199 | // locking device | 198 | // locking device |
200 | synchronized (providerDescs) { | 199 | synchronized (providerDescs) { |
... | @@ -219,7 +218,7 @@ public class SimpleDeviceStore | ... | @@ -219,7 +218,7 @@ public class SimpleDeviceStore |
219 | Device device = devices.get(deviceId); | 218 | Device device = devices.get(deviceId); |
220 | checkArgument(device != null, DEVICE_NOT_FOUND, deviceId); | 219 | checkArgument(device != null, DEVICE_NOT_FOUND, deviceId); |
221 | 220 | ||
222 | - ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId); | 221 | + Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId); |
223 | checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); | 222 | checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); |
224 | 223 | ||
225 | List<DeviceEvent> events = new ArrayList<>(); | 224 | List<DeviceEvent> events = new ArrayList<>(); |
... | @@ -288,12 +287,12 @@ public class SimpleDeviceStore | ... | @@ -288,12 +287,12 @@ public class SimpleDeviceStore |
288 | Map<PortNumber, Port> ports, | 287 | Map<PortNumber, Port> ports, |
289 | Set<PortNumber> processed) { | 288 | Set<PortNumber> processed) { |
290 | List<DeviceEvent> events = new ArrayList<>(); | 289 | List<DeviceEvent> events = new ArrayList<>(); |
291 | - Iterator<PortNumber> iterator = ports.keySet().iterator(); | 290 | + Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator(); |
292 | while (iterator.hasNext()) { | 291 | while (iterator.hasNext()) { |
293 | - PortNumber portNumber = iterator.next(); | 292 | + Entry<PortNumber, Port> e = iterator.next(); |
293 | + PortNumber portNumber = e.getKey(); | ||
294 | if (!processed.contains(portNumber)) { | 294 | if (!processed.contains(portNumber)) { |
295 | - events.add(new DeviceEvent(PORT_REMOVED, device, | 295 | + events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue())); |
296 | - ports.get(portNumber))); | ||
297 | iterator.remove(); | 296 | iterator.remove(); |
298 | } | 297 | } |
299 | } | 298 | } |
... | @@ -307,10 +306,36 @@ public class SimpleDeviceStore | ... | @@ -307,10 +306,36 @@ public class SimpleDeviceStore |
307 | NewConcurrentHashMap.<PortNumber, Port>ifNeeded()); | 306 | NewConcurrentHashMap.<PortNumber, Port>ifNeeded()); |
308 | } | 307 | } |
309 | 308 | ||
310 | - private ConcurrentMap<ProviderId, DeviceDescriptions> getDeviceDescriptions( | 309 | + private Map<ProviderId, DeviceDescriptions> getOrCreateDeviceDescriptions( |
311 | DeviceId deviceId) { | 310 | DeviceId deviceId) { |
312 | - return createIfAbsentUnchecked(deviceDescs, deviceId, | 311 | + Map<ProviderId, DeviceDescriptions> r; |
313 | - NewConcurrentHashMap.<ProviderId, DeviceDescriptions>ifNeeded()); | 312 | + r = deviceDescs.get(deviceId); |
313 | + if (r != null) { | ||
314 | + return r; | ||
315 | + } | ||
316 | + r = new HashMap<>(); | ||
317 | + final Map<ProviderId, DeviceDescriptions> concurrentlyAdded; | ||
318 | + concurrentlyAdded = deviceDescs.putIfAbsent(deviceId, r); | ||
319 | + if (concurrentlyAdded != null) { | ||
320 | + return concurrentlyAdded; | ||
321 | + } else { | ||
322 | + return r; | ||
323 | + } | ||
324 | + } | ||
325 | + | ||
326 | + // Guarded by deviceDescs value (=Device lock) | ||
327 | + private DeviceDescriptions getOrCreateProviderDeviceDescriptions( | ||
328 | + Map<ProviderId, DeviceDescriptions> device, | ||
329 | + ProviderId providerId, DeviceDescription deltaDesc) { | ||
330 | + | ||
331 | + synchronized (device) { | ||
332 | + DeviceDescriptions r = device.get(providerId); | ||
333 | + if (r == null) { | ||
334 | + r = new DeviceDescriptions(deltaDesc); | ||
335 | + device.put(providerId, r); | ||
336 | + } | ||
337 | + return r; | ||
338 | + } | ||
314 | } | 339 | } |
315 | 340 | ||
316 | @Override | 341 | @Override |
... | @@ -319,12 +344,12 @@ public class SimpleDeviceStore | ... | @@ -319,12 +344,12 @@ public class SimpleDeviceStore |
319 | Device device = devices.get(deviceId); | 344 | Device device = devices.get(deviceId); |
320 | checkArgument(device != null, DEVICE_NOT_FOUND, deviceId); | 345 | checkArgument(device != null, DEVICE_NOT_FOUND, deviceId); |
321 | 346 | ||
322 | - ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId); | 347 | + Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId); |
323 | checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); | 348 | checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); |
324 | 349 | ||
325 | synchronized (descsMap) { | 350 | synchronized (descsMap) { |
326 | DeviceDescriptions descs = descsMap.get(providerId); | 351 | DeviceDescriptions descs = descsMap.get(providerId); |
327 | - // assuming all providers must to give DeviceDescription | 352 | + // assuming all providers must give DeviceDescription first |
328 | checkArgument(descs != null, | 353 | checkArgument(descs != null, |
329 | "Device description for Device ID %s from Provider %s was not found", | 354 | "Device description for Device ID %s from Provider %s was not found", |
330 | deviceId, providerId); | 355 | deviceId, providerId); |
... | @@ -368,7 +393,7 @@ public class SimpleDeviceStore | ... | @@ -368,7 +393,7 @@ public class SimpleDeviceStore |
368 | 393 | ||
369 | @Override | 394 | @Override |
370 | public DeviceEvent removeDevice(DeviceId deviceId) { | 395 | public DeviceEvent removeDevice(DeviceId deviceId) { |
371 | - ConcurrentMap<ProviderId, DeviceDescriptions> descs = getDeviceDescriptions(deviceId); | 396 | + Map<ProviderId, DeviceDescriptions> descs = getOrCreateDeviceDescriptions(deviceId); |
372 | synchronized (descs) { | 397 | synchronized (descs) { |
373 | Device device = devices.remove(deviceId); | 398 | Device device = devices.remove(deviceId); |
374 | // should DEVICE_REMOVED carry removed ports? | 399 | // should DEVICE_REMOVED carry removed ports? |
... | @@ -391,7 +416,7 @@ public class SimpleDeviceStore | ... | @@ -391,7 +416,7 @@ public class SimpleDeviceStore |
391 | * @return Device instance | 416 | * @return Device instance |
392 | */ | 417 | */ |
393 | private Device composeDevice(DeviceId deviceId, | 418 | private Device composeDevice(DeviceId deviceId, |
394 | - ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) { | 419 | + Map<ProviderId, DeviceDescriptions> providerDescs) { |
395 | 420 | ||
396 | checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied"); | 421 | checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied"); |
397 | 422 | ||
... | @@ -432,14 +457,14 @@ public class SimpleDeviceStore | ... | @@ -432,14 +457,14 @@ public class SimpleDeviceStore |
432 | * | 457 | * |
433 | * @param device device the port is on | 458 | * @param device device the port is on |
434 | * @param number port number | 459 | * @param number port number |
435 | - * @param providerDescs Collection of Descriptions from multiple providers | 460 | + * @param descsMap Collection of Descriptions from multiple providers |
436 | * @return Port instance | 461 | * @return Port instance |
437 | */ | 462 | */ |
438 | private Port composePort(Device device, PortNumber number, | 463 | private Port composePort(Device device, PortNumber number, |
439 | - ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) { | 464 | + Map<ProviderId, DeviceDescriptions> descsMap) { |
440 | 465 | ||
441 | - ProviderId primary = pickPrimaryPID(providerDescs); | 466 | + ProviderId primary = pickPrimaryPID(descsMap); |
442 | - DeviceDescriptions primDescs = providerDescs.get(primary); | 467 | + DeviceDescriptions primDescs = descsMap.get(primary); |
443 | // if no primary, assume not enabled | 468 | // if no primary, assume not enabled |
444 | // TODO: revisit this default port enabled/disabled behavior | 469 | // TODO: revisit this default port enabled/disabled behavior |
445 | boolean isEnabled = false; | 470 | boolean isEnabled = false; |
... | @@ -451,7 +476,7 @@ public class SimpleDeviceStore | ... | @@ -451,7 +476,7 @@ public class SimpleDeviceStore |
451 | annotations = merge(annotations, portDesc.annotations()); | 476 | annotations = merge(annotations, portDesc.annotations()); |
452 | } | 477 | } |
453 | 478 | ||
454 | - for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) { | 479 | + for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) { |
455 | if (e.getKey().equals(primary)) { | 480 | if (e.getKey().equals(primary)) { |
456 | continue; | 481 | continue; |
457 | } | 482 | } |
... | @@ -473,10 +498,9 @@ public class SimpleDeviceStore | ... | @@ -473,10 +498,9 @@ public class SimpleDeviceStore |
473 | /** | 498 | /** |
474 | * @return primary ProviderID, or randomly chosen one if none exists | 499 | * @return primary ProviderID, or randomly chosen one if none exists |
475 | */ | 500 | */ |
476 | - private ProviderId pickPrimaryPID( | 501 | + private ProviderId pickPrimaryPID(Map<ProviderId, DeviceDescriptions> descsMap) { |
477 | - ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) { | ||
478 | ProviderId fallBackPrimary = null; | 502 | ProviderId fallBackPrimary = null; |
479 | - for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) { | 503 | + for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) { |
480 | if (!e.getKey().isAncillary()) { | 504 | if (!e.getKey().isAncillary()) { |
481 | return e.getKey(); | 505 | return e.getKey(); |
482 | } else if (fallBackPrimary == null) { | 506 | } else if (fallBackPrimary == null) { |
... | @@ -487,21 +511,6 @@ public class SimpleDeviceStore | ... | @@ -487,21 +511,6 @@ public class SimpleDeviceStore |
487 | return fallBackPrimary; | 511 | return fallBackPrimary; |
488 | } | 512 | } |
489 | 513 | ||
490 | - public static final class InitDeviceDescs | ||
491 | - implements ConcurrentInitializer<DeviceDescriptions> { | ||
492 | - | ||
493 | - private final DeviceDescription deviceDesc; | ||
494 | - | ||
495 | - public InitDeviceDescs(DeviceDescription deviceDesc) { | ||
496 | - this.deviceDesc = checkNotNull(deviceDesc); | ||
497 | - } | ||
498 | - @Override | ||
499 | - public DeviceDescriptions get() throws ConcurrentException { | ||
500 | - return new DeviceDescriptions(deviceDesc); | ||
501 | - } | ||
502 | - } | ||
503 | - | ||
504 | - | ||
505 | /** | 514 | /** |
506 | * Collection of Description of a Device and it's Ports given from a Provider. | 515 | * Collection of Description of a Device and it's Ports given from a Provider. |
507 | */ | 516 | */ | ... | ... |
... | @@ -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); | ... | ... |
1 | package org.onlab.onos.store.trivial.impl; | 1 | package org.onlab.onos.store.trivial.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.SetMultimap; | 6 | import com.google.common.collect.SetMultimap; |
8 | 7 | ||
9 | -import org.apache.commons.lang3.concurrent.ConcurrentUtils; | ||
10 | import org.apache.felix.scr.annotations.Activate; | 8 | import org.apache.felix.scr.annotations.Activate; |
11 | import org.apache.felix.scr.annotations.Component; | 9 | import org.apache.felix.scr.annotations.Component; |
12 | import org.apache.felix.scr.annotations.Deactivate; | 10 | import org.apache.felix.scr.annotations.Deactivate; |
... | @@ -20,7 +18,6 @@ import org.onlab.onos.net.Link; | ... | @@ -20,7 +18,6 @@ import org.onlab.onos.net.Link; |
20 | import org.onlab.onos.net.SparseAnnotations; | 18 | import org.onlab.onos.net.SparseAnnotations; |
21 | import org.onlab.onos.net.Link.Type; | 19 | import org.onlab.onos.net.Link.Type; |
22 | import org.onlab.onos.net.LinkKey; | 20 | import org.onlab.onos.net.LinkKey; |
23 | -import org.onlab.onos.net.Provided; | ||
24 | import org.onlab.onos.net.link.DefaultLinkDescription; | 21 | import org.onlab.onos.net.link.DefaultLinkDescription; |
25 | import org.onlab.onos.net.link.LinkDescription; | 22 | import org.onlab.onos.net.link.LinkDescription; |
26 | import org.onlab.onos.net.link.LinkEvent; | 23 | import org.onlab.onos.net.link.LinkEvent; |
... | @@ -28,11 +25,12 @@ import org.onlab.onos.net.link.LinkStore; | ... | @@ -28,11 +25,12 @@ import org.onlab.onos.net.link.LinkStore; |
28 | import org.onlab.onos.net.link.LinkStoreDelegate; | 25 | import org.onlab.onos.net.link.LinkStoreDelegate; |
29 | import org.onlab.onos.net.provider.ProviderId; | 26 | import org.onlab.onos.net.provider.ProviderId; |
30 | import org.onlab.onos.store.AbstractStore; | 27 | import org.onlab.onos.store.AbstractStore; |
31 | -import org.onlab.util.NewConcurrentHashMap; | ||
32 | import org.slf4j.Logger; | 28 | import org.slf4j.Logger; |
33 | 29 | ||
34 | import java.util.Collections; | 30 | import java.util.Collections; |
31 | +import java.util.HashMap; | ||
35 | import java.util.HashSet; | 32 | import java.util.HashSet; |
33 | +import java.util.Map; | ||
36 | import java.util.Set; | 34 | import java.util.Set; |
37 | import java.util.Map.Entry; | 35 | import java.util.Map.Entry; |
38 | import java.util.concurrent.ConcurrentHashMap; | 36 | import java.util.concurrent.ConcurrentHashMap; |
... | @@ -47,6 +45,7 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*; | ... | @@ -47,6 +45,7 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*; |
47 | import static org.slf4j.LoggerFactory.getLogger; | 45 | import static org.slf4j.LoggerFactory.getLogger; |
48 | import static com.google.common.collect.Multimaps.synchronizedSetMultimap; | 46 | import static com.google.common.collect.Multimaps.synchronizedSetMultimap; |
49 | import static com.google.common.base.Predicates.notNull; | 47 | import static com.google.common.base.Predicates.notNull; |
48 | +import static com.google.common.base.Verify.verifyNotNull; | ||
50 | 49 | ||
51 | /** | 50 | /** |
52 | * Manages inventory of infrastructure links using trivial in-memory structures | 51 | * Manages inventory of infrastructure links using trivial in-memory structures |
... | @@ -61,8 +60,7 @@ public class SimpleLinkStore | ... | @@ -61,8 +60,7 @@ public class SimpleLinkStore |
61 | private final Logger log = getLogger(getClass()); | 60 | private final Logger log = getLogger(getClass()); |
62 | 61 | ||
63 | // Link inventory | 62 | // Link inventory |
64 | - private final ConcurrentMap<LinkKey, | 63 | + private final ConcurrentMap<LinkKey, Map<ProviderId, LinkDescription>> |
65 | - ConcurrentMap<ProviderId, LinkDescription>> | ||
66 | linkDescs = new ConcurrentHashMap<>(); | 64 | linkDescs = new ConcurrentHashMap<>(); |
67 | 65 | ||
68 | // Link instance cache | 66 | // Link instance cache |
... | @@ -151,7 +149,7 @@ public class SimpleLinkStore | ... | @@ -151,7 +149,7 @@ public class SimpleLinkStore |
151 | LinkDescription linkDescription) { | 149 | LinkDescription linkDescription) { |
152 | LinkKey key = linkKey(linkDescription.src(), linkDescription.dst()); | 150 | LinkKey key = linkKey(linkDescription.src(), linkDescription.dst()); |
153 | 151 | ||
154 | - ConcurrentMap<ProviderId, LinkDescription> descs = getLinkDescriptions(key); | 152 | + Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key); |
155 | synchronized (descs) { | 153 | synchronized (descs) { |
156 | final Link oldLink = links.get(key); | 154 | final Link oldLink = links.get(key); |
157 | // update description | 155 | // update description |
... | @@ -166,7 +164,7 @@ public class SimpleLinkStore | ... | @@ -166,7 +164,7 @@ public class SimpleLinkStore |
166 | 164 | ||
167 | // Guarded by linkDescs value (=locking each Link) | 165 | // Guarded by linkDescs value (=locking each Link) |
168 | private LinkDescription createOrUpdateLinkDescription( | 166 | private LinkDescription createOrUpdateLinkDescription( |
169 | - ConcurrentMap<ProviderId, LinkDescription> descs, | 167 | + Map<ProviderId, LinkDescription> descs, |
170 | ProviderId providerId, | 168 | ProviderId providerId, |
171 | LinkDescription linkDescription) { | 169 | LinkDescription linkDescription) { |
172 | 170 | ||
... | @@ -227,7 +225,7 @@ public class SimpleLinkStore | ... | @@ -227,7 +225,7 @@ public class SimpleLinkStore |
227 | @Override | 225 | @Override |
228 | public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) { | 226 | public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) { |
229 | final LinkKey key = linkKey(src, dst); | 227 | final LinkKey key = linkKey(src, dst); |
230 | - ConcurrentMap<ProviderId, LinkDescription> descs = getLinkDescriptions(key); | 228 | + Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key); |
231 | synchronized (descs) { | 229 | synchronized (descs) { |
232 | Link link = links.remove(key); | 230 | Link link = links.remove(key); |
233 | descs.clear(); | 231 | descs.clear(); |
... | @@ -247,8 +245,8 @@ public class SimpleLinkStore | ... | @@ -247,8 +245,8 @@ public class SimpleLinkStore |
247 | /** | 245 | /** |
248 | * @return primary ProviderID, or randomly chosen one if none exists | 246 | * @return primary ProviderID, or randomly chosen one if none exists |
249 | */ | 247 | */ |
250 | - private ProviderId pickPrimaryPID( | 248 | + // Guarded by linkDescs value (=locking each Link) |
251 | - ConcurrentMap<ProviderId, LinkDescription> providerDescs) { | 249 | + private ProviderId getBaseProviderId(Map<ProviderId, LinkDescription> providerDescs) { |
252 | 250 | ||
253 | ProviderId fallBackPrimary = null; | 251 | ProviderId fallBackPrimary = null; |
254 | for (Entry<ProviderId, LinkDescription> e : providerDescs.entrySet()) { | 252 | for (Entry<ProviderId, LinkDescription> e : providerDescs.entrySet()) { |
... | @@ -262,9 +260,10 @@ public class SimpleLinkStore | ... | @@ -262,9 +260,10 @@ public class SimpleLinkStore |
262 | return fallBackPrimary; | 260 | return fallBackPrimary; |
263 | } | 261 | } |
264 | 262 | ||
265 | - private Link composeLink(ConcurrentMap<ProviderId, LinkDescription> descs) { | 263 | + // Guarded by linkDescs value (=locking each Link) |
266 | - ProviderId primary = pickPrimaryPID(descs); | 264 | + private Link composeLink(Map<ProviderId, LinkDescription> descs) { |
267 | - LinkDescription base = descs.get(primary); | 265 | + ProviderId primary = getBaseProviderId(descs); |
266 | + LinkDescription base = descs.get(verifyNotNull(primary)); | ||
268 | 267 | ||
269 | ConnectPoint src = base.src(); | 268 | ConnectPoint src = base.src(); |
270 | ConnectPoint dst = base.dst(); | 269 | ConnectPoint dst = base.dst(); |
... | @@ -289,9 +288,20 @@ public class SimpleLinkStore | ... | @@ -289,9 +288,20 @@ public class SimpleLinkStore |
289 | return new DefaultLink(primary , src, dst, type, annotations); | 288 | return new DefaultLink(primary , src, dst, type, annotations); |
290 | } | 289 | } |
291 | 290 | ||
292 | - private ConcurrentMap<ProviderId, LinkDescription> getLinkDescriptions(LinkKey key) { | 291 | + private Map<ProviderId, LinkDescription> getOrCreateLinkDescriptions(LinkKey key) { |
293 | - return ConcurrentUtils.createIfAbsentUnchecked(linkDescs, key, | 292 | + Map<ProviderId, LinkDescription> r; |
294 | - NewConcurrentHashMap.<ProviderId, LinkDescription>ifNeeded()); | 293 | + r = linkDescs.get(key); |
294 | + if (r != null) { | ||
295 | + return r; | ||
296 | + } | ||
297 | + r = new HashMap<>(); | ||
298 | + final Map<ProviderId, LinkDescription> concurrentlyAdded; | ||
299 | + concurrentlyAdded = linkDescs.putIfAbsent(key, r); | ||
300 | + if (concurrentlyAdded == null) { | ||
301 | + return r; | ||
302 | + } else { | ||
303 | + return concurrentlyAdded; | ||
304 | + } | ||
295 | } | 305 | } |
296 | 306 | ||
297 | private final Function<LinkKey, Link> lookupLink = new LookupLink(); | 307 | private final Function<LinkKey, Link> lookupLink = new LookupLink(); |
... | @@ -302,20 +312,11 @@ public class SimpleLinkStore | ... | @@ -302,20 +312,11 @@ public class SimpleLinkStore |
302 | private final class LookupLink implements Function<LinkKey, Link> { | 312 | private final class LookupLink implements Function<LinkKey, Link> { |
303 | @Override | 313 | @Override |
304 | public Link apply(LinkKey input) { | 314 | public Link apply(LinkKey input) { |
305 | - return links.get(input); | 315 | + if (input == null) { |
306 | - } | 316 | + return null; |
307 | - } | 317 | + } else { |
308 | - | 318 | + return links.get(input); |
309 | - private static final Predicate<Provided> IS_PRIMARY = new IsPrimary(); | 319 | + } |
310 | - private static final Predicate<Provided> isPrimary() { | ||
311 | - return IS_PRIMARY; | ||
312 | - } | ||
313 | - | ||
314 | - private static final class IsPrimary implements Predicate<Provided> { | ||
315 | - | ||
316 | - @Override | ||
317 | - public boolean apply(Provided input) { | ||
318 | - return !input.providerId().isAncillary(); | ||
319 | } | 320 | } |
320 | } | 321 | } |
321 | } | 322 | } | ... | ... |
1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
2 | <features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" | 2 | <features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" |
3 | name="onos-1.0.0"> | 3 | name="onos-1.0.0"> |
4 | - <repository>mvn:org.onlab.onos/onos-features/1.0.0-SNAPSHOT/xml/features</repository> | 4 | + <repository>mvn:org.onlab.onos/onos-features/1.0.0-SNAPSHOT/xml/features |
5 | + </repository> | ||
5 | 6 | ||
6 | <feature name="onos-thirdparty-base" version="1.0.0" | 7 | <feature name="onos-thirdparty-base" version="1.0.0" |
7 | description="ONOS 3rd party dependencies"> | 8 | description="ONOS 3rd party dependencies"> |
... | @@ -28,20 +29,22 @@ | ... | @@ -28,20 +29,22 @@ |
28 | 29 | ||
29 | <bundle>mvn:org.onlab.onos/onlab-nio/1.0.0-SNAPSHOT</bundle> | 30 | <bundle>mvn:org.onlab.onos/onlab-nio/1.0.0-SNAPSHOT</bundle> |
30 | 31 | ||
31 | - <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle> | 32 | + <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> | 33 | + <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle> |
34 | + <bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle> | ||
33 | </feature> | 35 | </feature> |
34 | 36 | ||
35 | <feature name="onos-thirdparty-web" version="1.0.0" | 37 | <feature name="onos-thirdparty-web" version="1.0.0" |
36 | description="ONOS 3rd party dependencies"> | 38 | description="ONOS 3rd party dependencies"> |
37 | <feature>war</feature> | 39 | <feature>war</feature> |
38 | <bundle>mvn:com.fasterxml.jackson.core/jackson-core/2.4.2</bundle> | 40 | <bundle>mvn:com.fasterxml.jackson.core/jackson-core/2.4.2</bundle> |
39 | - <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.2</bundle> | 41 | + <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.2 |
42 | + </bundle> | ||
40 | <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/2.4.2</bundle> | 43 | <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/2.4.2</bundle> |
41 | <bundle>mvn:com.sun.jersey/jersey-core/1.18.1</bundle> | 44 | <bundle>mvn:com.sun.jersey/jersey-core/1.18.1</bundle> |
42 | <bundle>mvn:com.sun.jersey/jersey-server/1.18.1</bundle> | 45 | <bundle>mvn:com.sun.jersey/jersey-server/1.18.1</bundle> |
43 | <bundle>mvn:com.sun.jersey/jersey-servlet/1.18.1</bundle> | 46 | <bundle>mvn:com.sun.jersey/jersey-servlet/1.18.1</bundle> |
44 | - | 47 | + |
45 | </feature> | 48 | </feature> |
46 | 49 | ||
47 | <feature name="onos-api" version="1.0.0" | 50 | <feature name="onos-api" version="1.0.0" |
... | @@ -95,7 +98,7 @@ | ... | @@ -95,7 +98,7 @@ |
95 | </feature> | 98 | </feature> |
96 | 99 | ||
97 | <feature name="onos-openflow" version="1.0.0" | 100 | <feature name="onos-openflow" version="1.0.0" |
98 | - description="ONOS OpenFlow API, Controller & Providers"> | 101 | + description="ONOS OpenFlow API, Controller & Providers"> |
99 | <feature>onos-api</feature> | 102 | <feature>onos-api</feature> |
100 | <bundle>mvn:io.netty/netty/3.9.2.Final</bundle> | 103 | <bundle>mvn:io.netty/netty/3.9.2.Final</bundle> |
101 | <bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle> | 104 | <bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle> |
... | @@ -159,4 +162,11 @@ | ... | @@ -159,4 +162,11 @@ |
159 | <bundle>mvn:org.onlab.onos/onos-app-sdnip/1.0.0-SNAPSHOT</bundle> | 162 | <bundle>mvn:org.onlab.onos/onos-app-sdnip/1.0.0-SNAPSHOT</bundle> |
160 | </feature> | 163 | </feature> |
161 | 164 | ||
165 | + <feature name="onos-app-calendar" version="1.0.0" | ||
166 | + description="REST interface for scheduling intents from an external calendar"> | ||
167 | + <feature>onos-api</feature> | ||
168 | + <feature>onos-thirdparty-web</feature> | ||
169 | + <bundle>mvn:org.onlab.onos/onos-app-calendar/1.0.0-SNAPSHOT</bundle> | ||
170 | + </feature> | ||
171 | + | ||
162 | </features> | 172 | </features> | ... | ... |
... | @@ -5,6 +5,7 @@ import org.onlab.onos.openflow.controller.driver.SwitchDriverSubHandshakeComplet | ... | @@ -5,6 +5,7 @@ import org.onlab.onos.openflow.controller.driver.SwitchDriverSubHandshakeComplet |
5 | import org.onlab.onos.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted; | 5 | import org.onlab.onos.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted; |
6 | import org.onlab.onos.openflow.controller.Dpid; | 6 | import org.onlab.onos.openflow.controller.Dpid; |
7 | import org.onlab.onos.openflow.controller.driver.AbstractOpenFlowSwitch; | 7 | import org.onlab.onos.openflow.controller.driver.AbstractOpenFlowSwitch; |
8 | +import org.projectfloodlight.openflow.protocol.OFBarrierRequest; | ||
8 | import org.projectfloodlight.openflow.protocol.OFCircuitPortsReply; | 9 | import org.projectfloodlight.openflow.protocol.OFCircuitPortsReply; |
9 | import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest; | 10 | import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest; |
10 | import org.projectfloodlight.openflow.protocol.OFDescStatsReply; | 11 | import org.projectfloodlight.openflow.protocol.OFDescStatsReply; |
... | @@ -21,7 +22,6 @@ import org.projectfloodlight.openflow.protocol.oxm.OFOxmInPort; | ... | @@ -21,7 +22,6 @@ import org.projectfloodlight.openflow.protocol.oxm.OFOxmInPort; |
21 | import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigid; | 22 | import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigid; |
22 | import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigidBasic; | 23 | import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigidBasic; |
23 | import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigtype; | 24 | import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigtype; |
24 | -import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigtypeBasic; | ||
25 | import org.projectfloodlight.openflow.types.CircuitSignalID; | 25 | import org.projectfloodlight.openflow.types.CircuitSignalID; |
26 | import org.projectfloodlight.openflow.types.OFPort; | 26 | import org.projectfloodlight.openflow.types.OFPort; |
27 | import org.projectfloodlight.openflow.types.U8; | 27 | import org.projectfloodlight.openflow.types.U8; |
... | @@ -119,11 +119,12 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -119,11 +119,12 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
119 | processHandshakeOFExperimenterPortDescRequest( | 119 | processHandshakeOFExperimenterPortDescRequest( |
120 | (OFCircuitPortsReply) m); | 120 | (OFCircuitPortsReply) m); |
121 | driverHandshakeComplete.set(true); | 121 | driverHandshakeComplete.set(true); |
122 | - /* try { | 122 | + try { |
123 | testMA(); | 123 | testMA(); |
124 | + testReverseMA(); | ||
124 | } catch (IOException e) { | 125 | } catch (IOException e) { |
125 | e.printStackTrace(); | 126 | e.printStackTrace(); |
126 | - }*/ | 127 | + } |
127 | break; | 128 | break; |
128 | default: | 129 | default: |
129 | log.debug("Received message {} during switch-driver " + | 130 | log.debug("Received message {} during switch-driver " + |
... | @@ -163,22 +164,71 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -163,22 +164,71 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
163 | "message " + | 164 | "message " + |
164 | "{}", | 165 | "{}", |
165 | circuitPortsRequest.toString()); | 166 | circuitPortsRequest.toString()); |
166 | - channel.write(Collections.singletonList(circuitPortsRequest)); | 167 | + sendMsg(Collections.<OFMessage>singletonList(circuitPortsRequest)); |
167 | } | 168 | } |
168 | 169 | ||
169 | 170 | ||
170 | - | 171 | + public static final U8 SIGNAL_TYPE = U8.of((short) 10); |
171 | - //todo for testing | ||
172 | - public static final U8 SIGNAL_TYPE = U8.of((short) 1); | ||
173 | private void testMA() throws IOException { | 172 | private void testMA() throws IOException { |
174 | log.debug("LINC OE *** Testing MA "); | 173 | log.debug("LINC OE *** Testing MA "); |
175 | - short lambda = 100; | 174 | + short lambda = 1; |
176 | - if (getId() == 0x0000ffffffffff02L) { | 175 | + if (getId() == 0x0000ffffffffff01L) { |
177 | final int inport = 10; | 176 | final int inport = 10; |
178 | final int outport = 20; | 177 | final int outport = 20; |
179 | //Circuit signal id | 178 | //Circuit signal id |
180 | CircuitSignalID sigID = getSignalID(lambda); | 179 | CircuitSignalID sigID = getSignalID(lambda); |
181 | 180 | ||
181 | + OFOxmOchSigidBasic ofOxmOchSigidBasic = | ||
182 | + factory().oxms().ochSigidBasic(sigID); | ||
183 | + | ||
184 | + | ||
185 | + //Match Port | ||
186 | + OFOxmInPort fieldPort = factory().oxms() | ||
187 | + .inPort(OFPort.of(inport)); | ||
188 | + OFMatchV3 matchPort = | ||
189 | + factory() | ||
190 | + .buildMatchV3(). | ||
191 | + setOxmList(OFOxmList.of(fieldPort)).build(); | ||
192 | + | ||
193 | + | ||
194 | + // Set Action outport ,sigType and sigID | ||
195 | + List<OFAction> actionList = new ArrayList<>(); | ||
196 | + OFAction actionOutPort = | ||
197 | + factory().actions().output(OFPort.of(outport), | ||
198 | + 0xffff); | ||
199 | + | ||
200 | + OFActionCircuit actionCircuit = factory() | ||
201 | + .actions() | ||
202 | + .circuit(ofOxmOchSigidBasic); | ||
203 | + | ||
204 | + actionList.add(actionCircuit); | ||
205 | + actionList.add(actionOutPort); | ||
206 | + | ||
207 | + OFInstruction instructionAction = | ||
208 | + factory().instructions().buildApplyActions() | ||
209 | + .setActions(actionList) | ||
210 | + .build(); | ||
211 | + List<OFInstruction> instructions = | ||
212 | + Collections.singletonList(instructionAction); | ||
213 | + | ||
214 | + OFMessage opticalFlowEntry = | ||
215 | + factory().buildFlowAdd() | ||
216 | + .setMatch(matchPort) | ||
217 | + .setPriority(100) | ||
218 | + .setInstructions(instructions) | ||
219 | + .setXid(getNextTransactionId()) | ||
220 | + .build(); | ||
221 | + log.debug("Adding optical flow in sw {}", getStringId()); | ||
222 | + List<OFMessage> msglist = new ArrayList<>(1); | ||
223 | + msglist.add(opticalFlowEntry); | ||
224 | + write(msglist); | ||
225 | + sendBarrier(true); | ||
226 | + } else if (getId() == 0x0000ffffffffff03L) { | ||
227 | + final int inport = 30; | ||
228 | + final int outport = 31; | ||
229 | + //Circuit signal id | ||
230 | + CircuitSignalID sigID = getSignalID(lambda); | ||
231 | + | ||
182 | OFOxmOchSigid fieldSigIDMatch = factory().oxms().ochSigid(sigID); | 232 | OFOxmOchSigid fieldSigIDMatch = factory().oxms().ochSigid(sigID); |
183 | OFOxmOchSigtype fieldSigType = factory() | 233 | OFOxmOchSigtype fieldSigType = factory() |
184 | .oxms() | 234 | .oxms() |
... | @@ -187,8 +237,119 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -187,8 +237,119 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
187 | OFOxmOchSigidBasic ofOxmOchSigidBasic = | 237 | OFOxmOchSigidBasic ofOxmOchSigidBasic = |
188 | factory().oxms().ochSigidBasic(sigID); | 238 | factory().oxms().ochSigidBasic(sigID); |
189 | 239 | ||
190 | - OFOxmOchSigtypeBasic ofOxmOchSigtypeBasic = | 240 | + //Match Port,SigType,SigID |
191 | - factory().oxms().ochSigtypeBasic(SIGNAL_TYPE); | 241 | + OFOxmInPort fieldPort = factory() |
242 | + .oxms() | ||
243 | + .inPort(OFPort.of(inport)); | ||
244 | + OFMatchV3 matchPort = factory() | ||
245 | + .buildMatchV3() | ||
246 | + .setOxmList(OFOxmList.of(fieldPort, | ||
247 | + fieldSigIDMatch, | ||
248 | + fieldSigType | ||
249 | + )) | ||
250 | + .build(); | ||
251 | + | ||
252 | + // Set Action outport ,SigType, sigID | ||
253 | + List<OFAction> actionList = new ArrayList<>(); | ||
254 | + OFAction actionOutPort = | ||
255 | + factory().actions().output(OFPort.of(outport), | ||
256 | + 0xffff); | ||
257 | + | ||
258 | + OFActionCircuit actionCircuit = factory() | ||
259 | + .actions() | ||
260 | + .circuit(ofOxmOchSigidBasic); | ||
261 | + | ||
262 | + | ||
263 | + | ||
264 | + //actionList.add(setActionSigType); | ||
265 | + actionList.add(actionCircuit); | ||
266 | + actionList.add(actionOutPort); | ||
267 | + | ||
268 | + OFInstruction instructionAction = | ||
269 | + factory().instructions().buildApplyActions() | ||
270 | + .setActions(actionList) | ||
271 | + .build(); | ||
272 | + List<OFInstruction> instructions = | ||
273 | + Collections.singletonList(instructionAction); | ||
274 | + | ||
275 | + OFMessage opticalFlowEntry = | ||
276 | + factory().buildFlowAdd() | ||
277 | + .setMatch(matchPort) | ||
278 | + .setPriority(100) | ||
279 | + .setInstructions(instructions) | ||
280 | + .setXid(getNextTransactionId()) | ||
281 | + .build(); | ||
282 | + log.debug("Adding optical flow in sw {}", getStringId()); | ||
283 | + List<OFMessage> msglist = new ArrayList<>(1); | ||
284 | + msglist.add(opticalFlowEntry); | ||
285 | + write(msglist); | ||
286 | + sendBarrier(true); | ||
287 | + | ||
288 | + } else if (getId() == 0x0000ffffffffff02L) { | ||
289 | + final int inport = 21; | ||
290 | + final int outport = 11; | ||
291 | + //Circuit signal id | ||
292 | + CircuitSignalID sigID = getSignalID(lambda); | ||
293 | + | ||
294 | + OFOxmOchSigid fieldSigIDMatch = factory().oxms().ochSigid(sigID); | ||
295 | + OFOxmOchSigtype fieldSigType = factory() | ||
296 | + .oxms() | ||
297 | + .ochSigtype(SIGNAL_TYPE); | ||
298 | + | ||
299 | + | ||
300 | + //Match Port, sig type and sig id | ||
301 | + OFOxmInPort fieldPort = factory() | ||
302 | + .oxms() | ||
303 | + .inPort(OFPort.of(inport)); | ||
304 | + OFMatchV3 matchPort = | ||
305 | + factory().buildMatchV3() | ||
306 | + .setOxmList(OFOxmList.of(fieldPort, | ||
307 | + fieldSigIDMatch, | ||
308 | + fieldSigType | ||
309 | + )) | ||
310 | + .build(); | ||
311 | + | ||
312 | + // Set Action outport | ||
313 | + List<OFAction> actionList = new ArrayList<>(); | ||
314 | + OFAction actionOutPort = | ||
315 | + factory().actions().output(OFPort.of(outport), | ||
316 | + 0xffff); | ||
317 | + | ||
318 | + actionList.add(actionOutPort); | ||
319 | + | ||
320 | + OFInstruction instructionAction = | ||
321 | + factory().instructions().buildApplyActions() | ||
322 | + .setActions(actionList) | ||
323 | + .build(); | ||
324 | + List<OFInstruction> instructions = | ||
325 | + Collections.singletonList(instructionAction); | ||
326 | + | ||
327 | + OFMessage opticalFlowEntry = | ||
328 | + factory().buildFlowAdd() | ||
329 | + .setMatch(matchPort) | ||
330 | + .setPriority(100) | ||
331 | + .setInstructions(instructions) | ||
332 | + .setXid(getNextTransactionId()) | ||
333 | + .build(); | ||
334 | + log.debug("Adding optical flow in sw {}", getStringId()); | ||
335 | + List<OFMessage> msglist = new ArrayList<>(1); | ||
336 | + msglist.add(opticalFlowEntry); | ||
337 | + write(msglist); | ||
338 | + sendBarrier(true); | ||
339 | + } | ||
340 | + | ||
341 | + } | ||
342 | + private void testReverseMA() throws IOException { | ||
343 | + log.debug("LINC OE *** Testing MA "); | ||
344 | + short lambda = 1; | ||
345 | + if (getId() == 0x0000ffffffffff02L) { | ||
346 | + final int inport = 11; | ||
347 | + final int outport = 21; | ||
348 | + //Circuit signal id | ||
349 | + CircuitSignalID sigID = getSignalID(lambda); | ||
350 | + | ||
351 | + OFOxmOchSigidBasic ofOxmOchSigidBasic = | ||
352 | + factory().oxms().ochSigidBasic(sigID); | ||
192 | 353 | ||
193 | //Match Port | 354 | //Match Port |
194 | OFOxmInPort fieldPort = factory().oxms() | 355 | OFOxmInPort fieldPort = factory().oxms() |
... | @@ -196,27 +357,20 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -196,27 +357,20 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
196 | OFMatchV3 matchPort = | 357 | OFMatchV3 matchPort = |
197 | factory() | 358 | factory() |
198 | .buildMatchV3(). | 359 | .buildMatchV3(). |
199 | - setOxmList(OFOxmList.of(fieldPort, | 360 | + setOxmList(OFOxmList.of(fieldPort)).build(); |
200 | - fieldSigType, | ||
201 | - fieldSigIDMatch)).build(); | ||
202 | 361 | ||
203 | 362 | ||
204 | // Set Action outport ,sigType and sigID | 363 | // Set Action outport ,sigType and sigID |
205 | List<OFAction> actionList = new ArrayList<>(); | 364 | List<OFAction> actionList = new ArrayList<>(); |
206 | OFAction actionOutPort = | 365 | OFAction actionOutPort = |
207 | factory().actions().output(OFPort.of(outport), | 366 | factory().actions().output(OFPort.of(outport), |
208 | - Short.MAX_VALUE); | 367 | + 0xffff); |
209 | 368 | ||
210 | OFActionCircuit actionCircuit = factory() | 369 | OFActionCircuit actionCircuit = factory() |
211 | .actions() | 370 | .actions() |
212 | .circuit(ofOxmOchSigidBasic); | 371 | .circuit(ofOxmOchSigidBasic); |
213 | - OFActionCircuit setActionSigType = factory() | ||
214 | - .actions() | ||
215 | - .circuit(ofOxmOchSigtypeBasic); | ||
216 | - | ||
217 | - actionList.add(actionOutPort); | ||
218 | - actionList.add(setActionSigType); | ||
219 | actionList.add(actionCircuit); | 372 | actionList.add(actionCircuit); |
373 | + actionList.add(actionOutPort); | ||
220 | 374 | ||
221 | OFInstruction instructionAction = | 375 | OFInstruction instructionAction = |
222 | factory().instructions().buildApplyActions() | 376 | factory().instructions().buildApplyActions() |
... | @@ -228,6 +382,7 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -228,6 +382,7 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
228 | OFMessage opticalFlowEntry = | 382 | OFMessage opticalFlowEntry = |
229 | factory().buildFlowAdd() | 383 | factory().buildFlowAdd() |
230 | .setMatch(matchPort) | 384 | .setMatch(matchPort) |
385 | + .setPriority(100) | ||
231 | .setInstructions(instructions) | 386 | .setInstructions(instructions) |
232 | .setXid(getNextTransactionId()) | 387 | .setXid(getNextTransactionId()) |
233 | .build(); | 388 | .build(); |
... | @@ -235,9 +390,10 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -235,9 +390,10 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
235 | List<OFMessage> msglist = new ArrayList<>(1); | 390 | List<OFMessage> msglist = new ArrayList<>(1); |
236 | msglist.add(opticalFlowEntry); | 391 | msglist.add(opticalFlowEntry); |
237 | write(msglist); | 392 | write(msglist); |
393 | + sendBarrier(true); | ||
238 | } else if (getId() == 0x0000ffffffffff03L) { | 394 | } else if (getId() == 0x0000ffffffffff03L) { |
239 | - final int inport = 21; | 395 | + final int inport = 31; |
240 | - final int outport = 22; | 396 | + final int outport = 30; |
241 | //Circuit signal id | 397 | //Circuit signal id |
242 | CircuitSignalID sigID = getSignalID(lambda); | 398 | CircuitSignalID sigID = getSignalID(lambda); |
243 | 399 | ||
... | @@ -249,9 +405,6 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -249,9 +405,6 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
249 | OFOxmOchSigidBasic ofOxmOchSigidBasic = | 405 | OFOxmOchSigidBasic ofOxmOchSigidBasic = |
250 | factory().oxms().ochSigidBasic(sigID); | 406 | factory().oxms().ochSigidBasic(sigID); |
251 | 407 | ||
252 | - OFOxmOchSigtypeBasic ofOxmOchSigtypeBasic = | ||
253 | - factory().oxms().ochSigtypeBasic(SIGNAL_TYPE); | ||
254 | - | ||
255 | //Match Port,SigType,SigID | 408 | //Match Port,SigType,SigID |
256 | OFOxmInPort fieldPort = factory() | 409 | OFOxmInPort fieldPort = factory() |
257 | .oxms() | 410 | .oxms() |
... | @@ -259,27 +412,22 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -259,27 +412,22 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
259 | OFMatchV3 matchPort = factory() | 412 | OFMatchV3 matchPort = factory() |
260 | .buildMatchV3() | 413 | .buildMatchV3() |
261 | .setOxmList(OFOxmList.of(fieldPort, | 414 | .setOxmList(OFOxmList.of(fieldPort, |
262 | - fieldSigType, | 415 | + fieldSigIDMatch, |
263 | - fieldSigIDMatch)) | 416 | + fieldSigType |
417 | + )) | ||
264 | .build(); | 418 | .build(); |
265 | 419 | ||
266 | // Set Action outport ,SigType, sigID | 420 | // Set Action outport ,SigType, sigID |
267 | List<OFAction> actionList = new ArrayList<>(); | 421 | List<OFAction> actionList = new ArrayList<>(); |
268 | OFAction actionOutPort = | 422 | OFAction actionOutPort = |
269 | factory().actions().output(OFPort.of(outport), | 423 | factory().actions().output(OFPort.of(outport), |
270 | - Short.MAX_VALUE); | 424 | + 0xffff); |
271 | - | ||
272 | - OFActionCircuit setActionSigType = factory() | ||
273 | - .actions() | ||
274 | - .circuit(ofOxmOchSigtypeBasic); | ||
275 | OFActionCircuit actionCircuit = factory() | 425 | OFActionCircuit actionCircuit = factory() |
276 | .actions() | 426 | .actions() |
277 | .circuit(ofOxmOchSigidBasic); | 427 | .circuit(ofOxmOchSigidBasic); |
278 | 428 | ||
279 | - | ||
280 | - actionList.add(actionOutPort); | ||
281 | - actionList.add(setActionSigType); | ||
282 | actionList.add(actionCircuit); | 429 | actionList.add(actionCircuit); |
430 | + actionList.add(actionOutPort); | ||
283 | 431 | ||
284 | OFInstruction instructionAction = | 432 | OFInstruction instructionAction = |
285 | factory().instructions().buildApplyActions() | 433 | factory().instructions().buildApplyActions() |
... | @@ -290,18 +438,20 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -290,18 +438,20 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
290 | 438 | ||
291 | OFMessage opticalFlowEntry = | 439 | OFMessage opticalFlowEntry = |
292 | factory().buildFlowAdd() | 440 | factory().buildFlowAdd() |
293 | - .setMatch(matchPort) | 441 | + .setMatch(matchPort) |
294 | - .setInstructions(instructions) | 442 | + .setPriority(100) |
295 | - .setXid(getNextTransactionId()) | 443 | + .setInstructions(instructions) |
296 | - .build(); | 444 | + .setXid(getNextTransactionId()) |
445 | + .build(); | ||
297 | log.debug("Adding optical flow in sw {}", getStringId()); | 446 | log.debug("Adding optical flow in sw {}", getStringId()); |
298 | List<OFMessage> msglist = new ArrayList<>(1); | 447 | List<OFMessage> msglist = new ArrayList<>(1); |
299 | msglist.add(opticalFlowEntry); | 448 | msglist.add(opticalFlowEntry); |
300 | write(msglist); | 449 | write(msglist); |
450 | + sendBarrier(true); | ||
301 | 451 | ||
302 | - } else if (getId() == 0x0000ffffffffff04L) { | 452 | + } else if (getId() == 0x0000ffffffffff01L) { |
303 | - final int inport = 23; | 453 | + final int inport = 20; |
304 | - final int outport = 11; | 454 | + final int outport = 10; |
305 | //Circuit signal id | 455 | //Circuit signal id |
306 | CircuitSignalID sigID = getSignalID(lambda); | 456 | CircuitSignalID sigID = getSignalID(lambda); |
307 | 457 | ||
... | @@ -318,15 +468,16 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -318,15 +468,16 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
318 | OFMatchV3 matchPort = | 468 | OFMatchV3 matchPort = |
319 | factory().buildMatchV3() | 469 | factory().buildMatchV3() |
320 | .setOxmList(OFOxmList.of(fieldPort, | 470 | .setOxmList(OFOxmList.of(fieldPort, |
321 | - fieldSigType, | 471 | + fieldSigIDMatch, |
322 | - fieldSigIDMatch)) | 472 | + fieldSigType |
473 | + )) | ||
323 | .build(); | 474 | .build(); |
324 | 475 | ||
325 | // Set Action outport | 476 | // Set Action outport |
326 | List<OFAction> actionList = new ArrayList<>(); | 477 | List<OFAction> actionList = new ArrayList<>(); |
327 | OFAction actionOutPort = | 478 | OFAction actionOutPort = |
328 | factory().actions().output(OFPort.of(outport), | 479 | factory().actions().output(OFPort.of(outport), |
329 | - Short.MAX_VALUE); | 480 | + 0xffff); |
330 | 481 | ||
331 | actionList.add(actionOutPort); | 482 | actionList.add(actionOutPort); |
332 | 483 | ||
... | @@ -339,18 +490,21 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -339,18 +490,21 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
339 | 490 | ||
340 | OFMessage opticalFlowEntry = | 491 | OFMessage opticalFlowEntry = |
341 | factory().buildFlowAdd() | 492 | factory().buildFlowAdd() |
342 | - .setMatch(matchPort) | 493 | + .setMatch(matchPort) |
343 | - .setInstructions(instructions) | 494 | + .setPriority(100) |
344 | - .setXid(getNextTransactionId()) | 495 | + .setInstructions(instructions) |
345 | - .build(); | 496 | + .setXid(getNextTransactionId()) |
497 | + .build(); | ||
346 | log.debug("Adding optical flow in sw {}", getStringId()); | 498 | log.debug("Adding optical flow in sw {}", getStringId()); |
347 | List<OFMessage> msglist = new ArrayList<>(1); | 499 | List<OFMessage> msglist = new ArrayList<>(1); |
348 | msglist.add(opticalFlowEntry); | 500 | msglist.add(opticalFlowEntry); |
349 | write(msglist); | 501 | write(msglist); |
502 | + sendBarrier(true); | ||
350 | } | 503 | } |
351 | 504 | ||
352 | } | 505 | } |
353 | 506 | ||
507 | + | ||
354 | // Todo remove - for testing purpose only | 508 | // Todo remove - for testing purpose only |
355 | private static CircuitSignalID getSignalID(short lambda) { | 509 | private static CircuitSignalID getSignalID(short lambda) { |
356 | byte myGrid = 1; | 510 | byte myGrid = 1; |
... | @@ -365,9 +519,21 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { | ... | @@ -365,9 +519,21 @@ public class OFOpticalSwitchImplLINC13 extends AbstractOpenFlowSwitch { |
365 | return signalID; | 519 | return signalID; |
366 | } | 520 | } |
367 | 521 | ||
522 | + private void sendBarrier(boolean finalBarrier) throws IOException { | ||
523 | + int xid = getNextTransactionId(); | ||
524 | + if (finalBarrier) { | ||
525 | + barrierXidToWaitFor = xid; | ||
526 | + } | ||
527 | + OFBarrierRequest br = factory() | ||
528 | + .buildBarrierRequest() | ||
529 | + .setXid(xid) | ||
530 | + .build(); | ||
531 | + sendMsg(br); | ||
532 | + } | ||
533 | + | ||
368 | @Override | 534 | @Override |
369 | public void write(OFMessage msg) { | 535 | public void write(OFMessage msg) { |
370 | - this.channel.write(msg); | 536 | + this.sendMsg(msg); |
371 | } | 537 | } |
372 | 538 | ||
373 | @Override | 539 | @Override | ... | ... |
... | @@ -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> |
... | @@ -502,7 +521,7 @@ | ... | @@ -502,7 +521,7 @@ |
502 | <group> | 521 | <group> |
503 | <title>Core Subsystems</title> | 522 | <title>Core Subsystems</title> |
504 | <packages> | 523 | <packages> |
505 | - org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl | 524 | + org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl:org.onlab.onos.json:org.onlab.onos.json.* |
506 | </packages> | 525 | </packages> |
507 | </group> | 526 | </group> |
508 | <group> | 527 | <group> |
... | @@ -527,10 +546,11 @@ | ... | @@ -527,10 +546,11 @@ |
527 | <group> | 546 | <group> |
528 | <title>Sample Applications</title> | 547 | <title>Sample Applications</title> |
529 | <packages> | 548 | <packages> |
530 | - org.onlab.onos.tvue:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.foo | 549 | + org.onlab.onos.tvue:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.foo:org.onlab.onos.calendar |
531 | </packages> | 550 | </packages> |
532 | </group> | 551 | </group> |
533 | </groups> | 552 | </groups> |
553 | + <excludePackageNames>org.onlab.thirdparty</excludePackageNames> | ||
534 | </configuration> | 554 | </configuration> |
535 | </plugin> | 555 | </plugin> |
536 | 556 | ... | ... |
... | @@ -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: | ... | ... |
... | @@ -66,6 +66,7 @@ import org.slf4j.Logger; | ... | @@ -66,6 +66,7 @@ import org.slf4j.Logger; |
66 | * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen | 66 | * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen |
67 | * for flow_removed messages | 67 | * for flow_removed messages |
68 | */ | 68 | */ |
69 | +@Deprecated | ||
69 | public class LinkDiscovery implements TimerTask { | 70 | public class LinkDiscovery implements TimerTask { |
70 | 71 | ||
71 | private final OpenFlowSwitch sw; | 72 | private final OpenFlowSwitch sw; |
... | @@ -339,9 +340,14 @@ public class LinkDiscovery implements TimerTask { | ... | @@ -339,9 +340,14 @@ public class LinkDiscovery implements TimerTask { |
339 | final Iterator<Integer> fastIterator = this.fastPorts.iterator(); | 340 | final Iterator<Integer> fastIterator = this.fastPorts.iterator(); |
340 | while (fastIterator.hasNext()) { | 341 | while (fastIterator.hasNext()) { |
341 | final Integer portNumber = fastIterator.next(); | 342 | final Integer portNumber = fastIterator.next(); |
343 | + OFPortDesc port = findPort(portNumber); | ||
344 | + if (port == null) { | ||
345 | + // port can be null | ||
346 | + // #removePort modifies `ports` outside synchronized block | ||
347 | + continue; | ||
348 | + } | ||
342 | final int probeCount = this.portProbeCount.get(portNumber) | 349 | final int probeCount = this.portProbeCount.get(portNumber) |
343 | .getAndIncrement(); | 350 | .getAndIncrement(); |
344 | - OFPortDesc port = findPort(portNumber); | ||
345 | if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) { | 351 | if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) { |
346 | this.log.debug("sending fast probe to port"); | 352 | this.log.debug("sending fast probe to port"); |
347 | 353 | ... | ... |
providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
... | @@ -35,6 +35,7 @@ import org.slf4j.Logger; | ... | @@ -35,6 +35,7 @@ import org.slf4j.Logger; |
35 | * infrastructure links. | 35 | * infrastructure links. |
36 | */ | 36 | */ |
37 | @Component(immediate = true) | 37 | @Component(immediate = true) |
38 | +@Deprecated | ||
38 | public class OpenFlowLinkProvider extends AbstractProvider implements LinkProvider { | 39 | public class OpenFlowLinkProvider extends AbstractProvider implements LinkProvider { |
39 | 40 | ||
40 | private final Logger log = getLogger(getClass()); | 41 | private final Logger log = getLogger(getClass()); | ... | ... |
... | @@ -9,6 +9,10 @@ export KARAF_ZIP=${KARAF_ZIP:-~/Downloads/apache-karaf-3.0.1.zip} | ... | @@ -9,6 +9,10 @@ export KARAF_ZIP=${KARAF_ZIP:-~/Downloads/apache-karaf-3.0.1.zip} |
9 | export KARAF_TAR=${KARAF_TAR:-~/Downloads/apache-karaf-3.0.1.tar.gz} | 9 | export KARAF_TAR=${KARAF_TAR:-~/Downloads/apache-karaf-3.0.1.tar.gz} |
10 | export KARAF_DIST=$(basename $KARAF_ZIP .zip) | 10 | export KARAF_DIST=$(basename $KARAF_ZIP .zip) |
11 | 11 | ||
12 | +# Add ONOS-specific directories to the exectable PATH | ||
13 | +export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin" | ||
14 | +export PATH="$PATH:$ONOS_ROOT/tools/build" | ||
15 | + | ||
12 | # Fallback build number us derived from from the user name & time | 16 | # Fallback build number us derived from from the user name & time |
13 | export BUILD_NUMBER=${BUILD_NUMBER:-$(id -un)~$(date +'%Y/%m/%d@%H:%M')} | 17 | export BUILD_NUMBER=${BUILD_NUMBER:-$(id -un)~$(date +'%Y/%m/%d@%H:%M')} |
14 | 18 | ||
... | @@ -21,6 +25,9 @@ export ONOS_STAGE=$ONOS_STAGE_ROOT/$ONOS_BITS | ... | @@ -21,6 +25,9 @@ export ONOS_STAGE=$ONOS_STAGE_ROOT/$ONOS_BITS |
21 | export ONOS_TAR=$ONOS_STAGE.tar.gz | 25 | export ONOS_TAR=$ONOS_STAGE.tar.gz |
22 | 26 | ||
23 | # 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 | ||
24 | export ONOS_INSTALL_DIR="/opt/onos" # Installation directory on remote | 31 | export ONOS_INSTALL_DIR="/opt/onos" # Installation directory on remote |
25 | export OCI="${OCI:-192.168.56.101}" # ONOS Controller Instance | 32 | export OCI="${OCI:-192.168.56.101}" # ONOS Controller Instance |
26 | 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' |
... | @@ -61,10 +66,10 @@ function cell { | ... | @@ -61,10 +66,10 @@ function cell { |
61 | if [ -n "$1" ]; then | 66 | if [ -n "$1" ]; then |
62 | [ ! -f $ONOS_ROOT/tools/test/cells/$1 ] && \ | 67 | [ ! -f $ONOS_ROOT/tools/test/cells/$1 ] && \ |
63 | echo "No such cell: $1" >&2 && return 1 | 68 | echo "No such cell: $1" >&2 && return 1 |
64 | - unset OC1 OC2 OC3 OC4 OC5 OC6 OC7 OC8 OC9 OCN OCI | 69 | + unset ONOS_CELL ONOS_NIC ONOS_FEATURES |
65 | - . $ONOS_ROOT/tools/test/cells/$1 | 70 | + unset OC0 OC1 OC2 OC3 OC4 OC5 OC6 OC7 OC8 OC9 OCN OCI |
66 | - export OCI=$OC1 | ||
67 | export ONOS_CELL=$1 | 71 | export ONOS_CELL=$1 |
72 | + . $ONOS_ROOT/tools/test/cells/$1 | ||
68 | cell | 73 | cell |
69 | else | 74 | else |
70 | env | egrep "ONOS_CELL" | 75 | env | egrep "ONOS_CELL" | ... | ... |
tools/dev/onos.cshrc
0 → 100644
1 | +#!/bin/tcsh | ||
2 | +# ONOS developer csh/tcsh profile conveniences | ||
3 | +# Simply include in your own $HOME/.cshrc file. E.g.: | ||
4 | +# | ||
5 | +# setenv ONOS_ROOT $HOME/onos | ||
6 | +# if ( -f $ONOS_ROOT/tools/dev/onos.cshrc ) then | ||
7 | +# source $ONOS_ROOT/tools/dev/onos.cshrc | ||
8 | +# endif | ||
9 | +# | ||
10 | + | ||
11 | +# Root of the ONOS source tree | ||
12 | +if ( ! $?ONOS_ROOT ) then | ||
13 | + setenv ONOS_ROOT $HOME/onos | ||
14 | +endif | ||
15 | + | ||
16 | +# Setup some environmental context for developers | ||
17 | +if ( ! $?JAVA_HOME ) then | ||
18 | + if ( -x /usr/libexec/java_home ) then | ||
19 | + setenv JAVA_HOME `/usr/libexec/java_home -v 1.7` | ||
20 | + else if ( -d /usr/lib/jvm/java-7-openjdk-amd64 ) then | ||
21 | + setenv JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64 | ||
22 | + endif | ||
23 | +endif | ||
24 | +if ( ! $?MAVEN ) then | ||
25 | + setenv MAVEN $HOME/Applications/apache-maven-3.2.2 | ||
26 | +endif | ||
27 | +if ( ! $?KARAF ) then | ||
28 | + setenv KARAF $HOME/Applications/apache-karaf-3.0.1 | ||
29 | +endif | ||
30 | +setenv KARAF_LOG $KARAF/data/log/karaf.log | ||
31 | + | ||
32 | +alias onos-setup-cell ' ( $ONOS_ROOT/tools/test/bin/onos-show-cell \!^ ) && setenv ONOS_CELL \!^' | ||
33 | + | ||
34 | +set path=( $path $ONOS_ROOT/tools/dev/bin $ONOS_ROOT/tools/test/bin ) | ||
35 | +set path=( $path $ONOS_ROOT/tools/build ) | ||
36 | +set path=( $path $KARAF/bin ) |
tools/test/bin/onos-list-cells
0 → 100755
1 | +#!/bin/bash | ||
2 | +# ----------------------------------------------------------------------------- | ||
3 | +# List available ONOS cells configuration. | ||
4 | +# ----------------------------------------------------------------------------- | ||
5 | + | ||
6 | +[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1 | ||
7 | +. $ONOS_ROOT/tools/build/envDefaults | ||
8 | + | ||
9 | +# Lists available cells | ||
10 | +for cell in $(ls -1 $ONOS_ROOT/tools/test/cells); do | ||
11 | + if [ ${cell} = "${ONOS_CELL}" ]; then | ||
12 | + cell_id="${cell} *" | ||
13 | + else | ||
14 | + cell_id="${cell}" | ||
15 | + fi | ||
16 | + cell_descr="$(grep '^#' $ONOS_ROOT/tools/test/cells/$cell | head -n 1)" | ||
17 | + printf "%-12s %s\n" "${cell_id}" "${cell_descr}" | ||
18 | +done |
... | @@ -6,4 +6,39 @@ | ... | @@ -6,4 +6,39 @@ |
6 | [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1 | 6 | [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1 |
7 | . $ONOS_ROOT/tools/build/envDefaults | 7 | . $ONOS_ROOT/tools/build/envDefaults |
8 | 8 | ||
9 | -ssh $ONOS_USER@${1:-$OCI} "sudo service onos ${2:-status}" | 9 | +function print_usage { |
10 | + command_name=`basename $0` | ||
11 | + echo "Remotely administer the ONOS service on a single node or the current ONOS cell." | ||
12 | + echo | ||
13 | + echo "Usage: $command_name <TARGET> [COMMAND]" | ||
14 | + echo " $command_name [-h | --help]" | ||
15 | + echo "Options:" | ||
16 | + echo " TARGET The target of the command" | ||
17 | + echo " COMMAND The command to execute. Default value is 'status'" | ||
18 | + echo " [-h | --help] Print this help" | ||
19 | + echo "" | ||
20 | + echo "TARGET: <hostname | --cell>" | ||
21 | + echo " hostname Execute on the specified host name" | ||
22 | + echo " --cell Execute on the current ONOS cell" | ||
23 | + echo "" | ||
24 | + echo "COMMAND: [start|stop|restart|status]" | ||
25 | + echo "" | ||
26 | +} | ||
27 | + | ||
28 | +# Print usage | ||
29 | +if [ "${1}" = "-h" -o "${1}" = "--help" ]; then | ||
30 | + print_usage | ||
31 | + exit 0 | ||
32 | +fi | ||
33 | + | ||
34 | +# Select the target | ||
35 | +if [ "${1}" = "--cell" ]; then | ||
36 | + nodes=$(env | sort | egrep "OC[0-9]+" | cut -d= -f2) | ||
37 | +else | ||
38 | + nodes=${1:-$OCI} | ||
39 | +fi | ||
40 | + | ||
41 | +# Execute the remote commands | ||
42 | +for node in $nodes; do | ||
43 | + ssh $ONOS_USER@${node} "sudo service onos ${2:-status}" | ||
44 | +done | ... | ... |
tools/test/bin/onos-show-cell
0 → 100755
1 | +#!/bin/bash | ||
2 | +# ----------------------------------------------------------------------------- | ||
3 | +# Print the configuration of an ONOS cell. | ||
4 | +# ----------------------------------------------------------------------------- | ||
5 | + | ||
6 | +[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1 | ||
7 | +. $ONOS_ROOT/tools/build/envDefaults | ||
8 | + | ||
9 | +function print_usage { | ||
10 | + echo "Print the configuration of an ONOS cell." | ||
11 | + echo "If no arguments are specified, it will print the configuration for the default" | ||
12 | + echo "ONOS cell as specified in the 'ONOS_CELL' environmental variable." | ||
13 | + echo | ||
14 | + echo "Optional arguments:" | ||
15 | + echo " [cell-name] Print the configuration of 'cell-name'" | ||
16 | + echo " [-h | --help] Print this help" | ||
17 | +} | ||
18 | + | ||
19 | +if [ "${1}" = "-h" -o "${1}" = "--help" ]; then | ||
20 | + print_usage | ||
21 | + exit 0 | ||
22 | +fi | ||
23 | + | ||
24 | +if [ -n "${1}" ]; then | ||
25 | + cell="${1}" | ||
26 | +else | ||
27 | + if [ -z "${ONOS_CELL}" ]; then | ||
28 | + echo "Environmental variable 'ONOS_CELL' is not defiled" | ||
29 | + exit 1 | ||
30 | + else | ||
31 | + cell="${ONOS_CELL}" | ||
32 | + fi | ||
33 | +fi | ||
34 | + | ||
35 | +if [ ! -f $ONOS_ROOT/tools/test/cells/${cell} ]; then | ||
36 | + echo "No such cell: ${cell}" | ||
37 | + exit 1 | ||
38 | +fi | ||
39 | + | ||
40 | +# Load the cell setup | ||
41 | +. $ONOS_ROOT/tools/test/cells/${cell} | ||
42 | + | ||
43 | +echo "ONOS_CELL=${ONOS_CELL}" | ||
44 | +echo "ONOS_NIC=${ONOS_NIC}" | ||
45 | +for n in {0..9}; do | ||
46 | + ocn="OC${n}" | ||
47 | + if [ -n "${!ocn}" ]; then | ||
48 | + echo "$ocn=${!ocn}" | ||
49 | + fi | ||
50 | +done | ||
51 | +echo "OCN=${OCN}" | ||
52 | +echo "OCI=${OCI}" | ||
53 | +echo "ONOS_FEATURES=${ONOS_FEATURES}" |
1 | # Local VirtualBox-based single ONOS instance & ONOS mininet box | 1 | # Local VirtualBox-based single ONOS instance & ONOS mininet box |
2 | 2 | ||
3 | +export ONOS_CELL="cbench" | ||
4 | + | ||
3 | export ONOS_NIC=192.168.56.* | 5 | export ONOS_NIC=192.168.56.* |
4 | export OC1="192.168.56.103" | 6 | export OC1="192.168.56.103" |
5 | export OCN="192.168.56.103" | 7 | export OCN="192.168.56.103" |
8 | +export OCI="${OC1}" | ||
6 | 9 | ||
7 | export ONOS_FEATURES="webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd" | 10 | export ONOS_FEATURES="webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd" | ... | ... |
1 | # Local VirtualBox-based ONOS instances 1,2 & ONOS mininet box | 1 | # Local VirtualBox-based ONOS instances 1,2 & ONOS mininet box |
2 | 2 | ||
3 | +export ONOS_CELL="local" | ||
4 | + | ||
3 | export ONOS_NIC=192.168.56.* | 5 | export ONOS_NIC=192.168.56.* |
4 | export OC1="192.168.56.101" | 6 | export OC1="192.168.56.101" |
5 | export OC2="192.168.56.102" | 7 | export OC2="192.168.56.102" |
6 | - | ||
7 | export OCN="192.168.56.103" | 8 | export OCN="192.168.56.103" |
9 | +export OCI="${OC1}" | ||
10 | + | ||
11 | +export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}" | ... | ... |
1 | # ProxMox-based cell of ONOS instance; no mininet-box | 1 | # ProxMox-based cell of ONOS instance; no mininet-box |
2 | 2 | ||
3 | -export ONOS_FEATURES="webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-mobility,onos-app-tvue,onos-app-proxyarp" | 3 | +export ONOS_CELL="office" |
4 | 4 | ||
5 | export ONOS_NIC="10.1.10.*" | 5 | export ONOS_NIC="10.1.10.*" |
6 | export OC1="10.1.10.223" | 6 | export OC1="10.1.10.223" |
7 | +export OCI="${OC1}" | ||
8 | + | ||
9 | +export ONOS_FEATURES="webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-mobility,onos-app-tvue,onos-app-proxyarp" | ... | ... |
1 | # ProxMox-based cell of ONOS instances 1,2 & ONOS mininet box | 1 | # ProxMox-based cell of ONOS instances 1,2 & ONOS mininet box |
2 | 2 | ||
3 | +export ONOS_CELL="prox" | ||
4 | + | ||
3 | export ONOS_NIC="10.1.9.*" | 5 | export ONOS_NIC="10.1.9.*" |
4 | export OC1="10.1.9.94" | 6 | export OC1="10.1.9.94" |
5 | export OC2="10.1.9.82" | 7 | export OC2="10.1.9.82" |
6 | - | ||
7 | export OCN="10.1.9.93" | 8 | export OCN="10.1.9.93" |
9 | +export OCI="${OC1}" | ||
10 | + | ||
11 | +export ONOS_FEATURES="" | ... | ... |
1 | # Local VirtualBox-based single ONOS instance & ONOS mininet box | 1 | # Local VirtualBox-based single ONOS instance & ONOS mininet box |
2 | 2 | ||
3 | +export ONOS_CELL="single" | ||
4 | + | ||
3 | export ONOS_NIC=192.168.56.* | 5 | export ONOS_NIC=192.168.56.* |
4 | export OC1="192.168.56.101" | 6 | export OC1="192.168.56.101" |
5 | export OCN="192.168.56.103" | 7 | export OCN="192.168.56.103" |
8 | +export OCI="${OC1}" | ||
9 | + | ||
10 | +export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}" | ... | ... |
1 | # Local VirtualBox-based ONOS instances 1,2,3 & ONOS mininet box | 1 | # Local VirtualBox-based ONOS instances 1,2,3 & ONOS mininet box |
2 | 2 | ||
3 | +export ONOS_CELL="triple" | ||
4 | + | ||
3 | export ONOS_NIC=192.168.56.* | 5 | export ONOS_NIC=192.168.56.* |
4 | export OC1="192.168.56.101" | 6 | export OC1="192.168.56.101" |
5 | export OC2="192.168.56.102" | 7 | export OC2="192.168.56.102" |
6 | export OC3="192.168.56.104" | 8 | export OC3="192.168.56.104" |
7 | - | ||
8 | export OCN="192.168.56.103" | 9 | export OCN="192.168.56.103" |
10 | +export OCI="${OC1}" | ||
11 | + | ||
12 | +export ONOS_FEATURES="" | ... | ... |
... | @@ -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; | ... | ... |
1 | +package org.onlab.util; | ||
2 | + | ||
3 | +import java.lang.reflect.Constructor; | ||
4 | +import java.lang.reflect.Field; | ||
5 | +import java.lang.reflect.InvocationTargetException; | ||
6 | +import java.lang.reflect.Method; | ||
7 | + | ||
8 | + | ||
9 | +/** | ||
10 | + * Utilities for testing. | ||
11 | + */ | ||
12 | +public final class TestUtils { | ||
13 | + | ||
14 | + /** | ||
15 | + * Sets the field, bypassing scope restriction. | ||
16 | + * | ||
17 | + * @param subject Object where the field belongs | ||
18 | + * @param fieldName name of the field to set | ||
19 | + * @param value value to set to the field. | ||
20 | + * @param <T> subject type | ||
21 | + * @param <U> value type | ||
22 | + * @throws TestUtilsException if there are reflection errors while setting | ||
23 | + * the field | ||
24 | + */ | ||
25 | + public static <T, U> void setField(T subject, String fieldName, U value) | ||
26 | + throws TestUtilsException { | ||
27 | + @SuppressWarnings("unchecked") | ||
28 | + Class<T> clazz = (Class<T>) subject.getClass(); | ||
29 | + try { | ||
30 | + Field field = clazz.getDeclaredField(fieldName); | ||
31 | + field.setAccessible(true); | ||
32 | + field.set(subject, value); | ||
33 | + } catch (NoSuchFieldException | SecurityException | | ||
34 | + IllegalArgumentException | IllegalAccessException e) { | ||
35 | + throw new TestUtilsException("setField failed", e); | ||
36 | + } | ||
37 | + } | ||
38 | + | ||
39 | + /** | ||
40 | + * Gets the field, bypassing scope restriction. | ||
41 | + * | ||
42 | + * @param subject Object where the field belongs | ||
43 | + * @param fieldName name of the field to get | ||
44 | + * @return value of the field. | ||
45 | + * @param <T> subject type | ||
46 | + * @param <U> field value type | ||
47 | + * @throws TestUtilsException if there are reflection errors while getting | ||
48 | + * the field | ||
49 | + */ | ||
50 | + public static <T, U> U getField(T subject, String fieldName) | ||
51 | + throws TestUtilsException { | ||
52 | + try { | ||
53 | + @SuppressWarnings("unchecked") | ||
54 | + Class<T> clazz = (Class<T>) subject.getClass(); | ||
55 | + Field field = clazz.getDeclaredField(fieldName); | ||
56 | + field.setAccessible(true); | ||
57 | + | ||
58 | + @SuppressWarnings("unchecked") | ||
59 | + U result = (U) field.get(subject); | ||
60 | + return result; | ||
61 | + } catch (NoSuchFieldException | SecurityException | | ||
62 | + IllegalArgumentException | IllegalAccessException e) { | ||
63 | + throw new TestUtilsException("getField failed", e); | ||
64 | + } | ||
65 | + } | ||
66 | + | ||
67 | + /** | ||
68 | + * Calls the method, bypassing scope restriction. | ||
69 | + * | ||
70 | + * @param subject Object where the method belongs | ||
71 | + * @param methodName name of the method to call | ||
72 | + * @param paramTypes formal parameter type array | ||
73 | + * @param args arguments | ||
74 | + * @return return value or null if void | ||
75 | + * @param <T> subject type | ||
76 | + * @param <U> return value type | ||
77 | + * @throws TestUtilsException if there are reflection errors while calling | ||
78 | + * the method | ||
79 | + */ | ||
80 | + public static <T, U> U callMethod(T subject, String methodName, | ||
81 | + Class<?>[] paramTypes, Object...args) throws TestUtilsException { | ||
82 | + | ||
83 | + try { | ||
84 | + @SuppressWarnings("unchecked") | ||
85 | + Class<T> clazz = (Class<T>) subject.getClass(); | ||
86 | + final Method method; | ||
87 | + if (paramTypes == null || paramTypes.length == 0) { | ||
88 | + method = clazz.getDeclaredMethod(methodName); | ||
89 | + } else { | ||
90 | + method = clazz.getDeclaredMethod(methodName, paramTypes); | ||
91 | + } | ||
92 | + method.setAccessible(true); | ||
93 | + | ||
94 | + @SuppressWarnings("unchecked") | ||
95 | + U result = (U) method.invoke(subject, args); | ||
96 | + return result; | ||
97 | + } catch (NoSuchMethodException | SecurityException | | ||
98 | + IllegalAccessException | IllegalArgumentException | | ||
99 | + InvocationTargetException e) { | ||
100 | + throw new TestUtilsException("callMethod failed", e); | ||
101 | + } | ||
102 | + } | ||
103 | + | ||
104 | + /** | ||
105 | + * Calls the method, bypassing scope restriction. | ||
106 | + * | ||
107 | + * @param subject Object where the method belongs | ||
108 | + * @param methodName name of the method to call | ||
109 | + * @param paramType formal parameter type | ||
110 | + * @param arg argument | ||
111 | + * @return return value or null if void | ||
112 | + * @param <T> subject type | ||
113 | + * @param <U> return value type | ||
114 | + * @throws TestUtilsException if there are reflection errors while calling | ||
115 | + * the method | ||
116 | + */ | ||
117 | + public static <T, U> U callMethod(T subject, String methodName, | ||
118 | + Class<?> paramType, Object arg) throws TestUtilsException { | ||
119 | + return callMethod(subject, methodName, new Class<?>[]{paramType}, arg); | ||
120 | + } | ||
121 | + | ||
122 | + /** | ||
123 | + * Triggers an allocation of an object of type <T> and forces a call to | ||
124 | + * the private constructor. | ||
125 | + * | ||
126 | + * @param constructor Constructor to call | ||
127 | + * @param <T> type of the object to create | ||
128 | + * @return created object of type <T> | ||
129 | + * @throws TestUtilsException if there are reflection errors while calling | ||
130 | + * the constructor | ||
131 | + */ | ||
132 | + public static <T> T callConstructor(Constructor<T> constructor) | ||
133 | + throws TestUtilsException { | ||
134 | + try { | ||
135 | + constructor.setAccessible(true); | ||
136 | + return constructor.newInstance(); | ||
137 | + } catch (InstantiationException | IllegalAccessException | | ||
138 | + InvocationTargetException error) { | ||
139 | + throw new TestUtilsException("callConstructor failed", error); | ||
140 | + } | ||
141 | + } | ||
142 | + | ||
143 | + /** | ||
144 | + * Avoid instantiation. | ||
145 | + */ | ||
146 | + private TestUtils() {} | ||
147 | + | ||
148 | + /** | ||
149 | + * Exception that can be thrown if problems are encountered while executing | ||
150 | + * the utility method. These are usually problems accessing fields/methods | ||
151 | + * through reflection. The original exception can be found by examining the | ||
152 | + * cause. | ||
153 | + */ | ||
154 | + public static class TestUtilsException extends Exception { | ||
155 | + | ||
156 | + private static final long serialVersionUID = 1L; | ||
157 | + | ||
158 | + /** | ||
159 | + * Constructs a new exception with the specified detail message and | ||
160 | + * cause. | ||
161 | + * | ||
162 | + * @param message the detail message | ||
163 | + * @param cause the original cause of this exception | ||
164 | + */ | ||
165 | + public TestUtilsException(String message, Throwable cause) { | ||
166 | + super(message, cause); | ||
167 | + } | ||
168 | + } | ||
169 | +} |
1 | +package org.onlab.util; | ||
2 | + | ||
3 | +import static org.junit.Assert.assertArrayEquals; | ||
4 | +import static org.junit.Assert.assertEquals; | ||
5 | +import static org.junit.Assert.assertNull; | ||
6 | + | ||
7 | +import org.junit.Before; | ||
8 | +import org.junit.Test; | ||
9 | +import org.onlab.util.TestUtils.TestUtilsException; | ||
10 | + | ||
11 | +/** | ||
12 | + * Test and usage examples for TestUtils. | ||
13 | + */ | ||
14 | +public class TestUtilsTest { | ||
15 | + | ||
16 | + /** | ||
17 | + * Test data. | ||
18 | + */ | ||
19 | + private static final class TestClass { | ||
20 | + | ||
21 | + @SuppressWarnings("unused") | ||
22 | + private int privateField = 42; | ||
23 | + | ||
24 | + @SuppressWarnings("unused") | ||
25 | + protected int protectedField = 2501; // CHECKSTYLE IGNORE THIS LINE | ||
26 | + | ||
27 | + /** | ||
28 | + * Protected method with multiple argument. | ||
29 | + * | ||
30 | + * @param x simply returns | ||
31 | + * @param y not used | ||
32 | + * @return x | ||
33 | + */ | ||
34 | + @SuppressWarnings("unused") | ||
35 | + private int privateMethod(Number x, Long y) { | ||
36 | + return x.intValue(); | ||
37 | + } | ||
38 | + | ||
39 | + /** | ||
40 | + * Protected method with no argument. | ||
41 | + * | ||
42 | + * @return int | ||
43 | + */ | ||
44 | + @SuppressWarnings("unused") | ||
45 | + protected int protectedMethod() { | ||
46 | + return 42; | ||
47 | + } | ||
48 | + | ||
49 | + /** | ||
50 | + * Method returning array. | ||
51 | + * | ||
52 | + * @param ary random array | ||
53 | + * @return ary | ||
54 | + */ | ||
55 | + @SuppressWarnings("unused") | ||
56 | + private int[] arrayReturnMethod(int[] ary) { | ||
57 | + return ary; | ||
58 | + } | ||
59 | + | ||
60 | + /** | ||
61 | + * Method without return value. | ||
62 | + * | ||
63 | + * @param s ignored | ||
64 | + */ | ||
65 | + @SuppressWarnings("unused") | ||
66 | + private void voidMethod(String s) { | ||
67 | + System.out.println(s); | ||
68 | + } | ||
69 | + } | ||
70 | + | ||
71 | + private TestClass test; | ||
72 | + | ||
73 | + /** | ||
74 | + * Sets up the test fixture. | ||
75 | + */ | ||
76 | + @Before | ||
77 | + public void setUp() { | ||
78 | + test = new TestClass(); | ||
79 | + } | ||
80 | + | ||
81 | + /** | ||
82 | + * Example to access private field. | ||
83 | + * | ||
84 | + * @throws TestUtilsException TestUtils error | ||
85 | + */ | ||
86 | + @Test | ||
87 | + public void testSetGetPrivateField() throws TestUtilsException { | ||
88 | + | ||
89 | + assertEquals(42, TestUtils.getField(test, "privateField")); | ||
90 | + TestUtils.setField(test, "privateField", 0xDEAD); | ||
91 | + assertEquals(0xDEAD, TestUtils.getField(test, "privateField")); | ||
92 | + } | ||
93 | + | ||
94 | + /** | ||
95 | + * Example to access protected field. | ||
96 | + * | ||
97 | + * @throws TestUtilsException TestUtils error | ||
98 | + */ | ||
99 | + @Test | ||
100 | + public void testSetGetProtectedField() throws TestUtilsException { | ||
101 | + | ||
102 | + assertEquals(2501, TestUtils.getField(test, "protectedField")); | ||
103 | + TestUtils.setField(test, "protectedField", 0xBEEF); | ||
104 | + assertEquals(0xBEEF, TestUtils.getField(test, "protectedField")); | ||
105 | + } | ||
106 | + | ||
107 | + /** | ||
108 | + * Example to call private method and multiple parameters. | ||
109 | + * <p/> | ||
110 | + * It also illustrates that paramTypes must match declared type, | ||
111 | + * not the runtime types of arguments. | ||
112 | + * | ||
113 | + * @throws TestUtilsException TestUtils error | ||
114 | + */ | ||
115 | + @Test | ||
116 | + public void testCallPrivateMethod() throws TestUtilsException { | ||
117 | + | ||
118 | + int result = TestUtils.callMethod(test, "privateMethod", | ||
119 | + new Class<?>[] {Number.class, Long.class}, | ||
120 | + Long.valueOf(42), Long.valueOf(32)); | ||
121 | + assertEquals(42, result); | ||
122 | + } | ||
123 | + | ||
124 | + /** | ||
125 | + * Example to call protected method and no parameters. | ||
126 | + * | ||
127 | + * @throws TestUtilsException TestUtils error | ||
128 | + */ | ||
129 | + @Test | ||
130 | + public void testCallProtectedMethod() throws TestUtilsException { | ||
131 | + | ||
132 | + int result = TestUtils.callMethod(test, "protectedMethod", | ||
133 | + new Class<?>[] {}); | ||
134 | + assertEquals(42, result); | ||
135 | + } | ||
136 | + | ||
137 | + /** | ||
138 | + * Example to call method returning array. | ||
139 | + * <p/> | ||
140 | + * Note: It is not required to receive as Object. | ||
141 | + * Following is just verifying it is not Boxed arrays. | ||
142 | + * | ||
143 | + * @throws TestUtilsException TestUtils error | ||
144 | + */ | ||
145 | + @Test | ||
146 | + public void testCallArrayReturnMethod() throws TestUtilsException { | ||
147 | + | ||
148 | + int[] array = {1, 2, 3}; | ||
149 | + Object aryResult = TestUtils.callMethod(test, "arrayReturnMethod", | ||
150 | + new Class<?>[] {int[].class}, array); | ||
151 | + assertEquals(int[].class, aryResult.getClass()); | ||
152 | + assertArrayEquals(array, (int[]) aryResult); | ||
153 | + } | ||
154 | + | ||
155 | + | ||
156 | + /** | ||
157 | + * Example to call void returning method. | ||
158 | + * <p/> | ||
159 | + * Note: Return value will be null for void methods. | ||
160 | + * | ||
161 | + * @throws TestUtilsException TestUtils error | ||
162 | + */ | ||
163 | + @Test | ||
164 | + public void testCallVoidReturnMethod() throws TestUtilsException { | ||
165 | + | ||
166 | + Object voidResult = TestUtils.callMethod(test, "voidMethod", | ||
167 | + String.class, "foobar"); | ||
168 | + assertNull(voidResult); | ||
169 | + } | ||
170 | +} |
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> |
... | @@ -2,6 +2,9 @@ | ... | @@ -2,6 +2,9 @@ |
2 | <html> | 2 | <html> |
3 | <head> | 3 | <head> |
4 | <title>ONOS GUI</title> | 4 | <title>ONOS GUI</title> |
5 | + | ||
6 | + <script src="libs/d3.min.js"></script> | ||
7 | + <script src="libs/jquery-2.1.1.min.js"></script> | ||
5 | </head> | 8 | </head> |
6 | <body> | 9 | <body> |
7 | <h1>ONOS GUI</h1> | 10 | <h1>ONOS GUI</h1> | ... | ... |
-
Please register or login to post a comment