Jonathan Hart

Unit tests for the ProxyArpManager.

...@@ -55,6 +55,7 @@ public class ProxyArpManager implements ProxyArpService { ...@@ -55,6 +55,7 @@ public class ProxyArpManager implements ProxyArpService {
55 private static final String REQUEST_NULL = "Arp request cannot be null."; 55 private static final String REQUEST_NULL = "Arp request cannot be null.";
56 private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request."; 56 private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request.";
57 private static final String NOT_ARP_REQUEST = "ARP is not a request."; 57 private static final String NOT_ARP_REQUEST = "ARP is not a request.";
58 + private static final String NOT_ARP_REPLY = "ARP is not a reply.";
58 59
59 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 60 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
60 protected HostService hostService; 61 protected HostService hostService;
...@@ -141,7 +142,7 @@ public class ProxyArpManager implements ProxyArpService { ...@@ -141,7 +142,7 @@ public class ProxyArpManager implements ProxyArpService {
141 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP, 142 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
142 REQUEST_NOT_ARP); 143 REQUEST_NOT_ARP);
143 ARP arp = (ARP) eth.getPayload(); 144 ARP arp = (ARP) eth.getPayload();
144 - checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REQUEST); 145 + checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REPLY);
145 146
146 Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(), 147 Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(),
147 VlanId.vlanId(eth.getVlanID()))); 148 VlanId.vlanId(eth.getVlanID())));
......
1 +package org.onlab.onos.net.proxyarp.impl;
2 +
3 +import static org.easymock.EasyMock.anyObject;
4 +import static org.easymock.EasyMock.createMock;
5 +import static org.easymock.EasyMock.expect;
6 +import static org.easymock.EasyMock.replay;
7 +import static org.junit.Assert.assertEquals;
8 +import static org.junit.Assert.assertFalse;
9 +import static org.junit.Assert.assertTrue;
10 +
11 +import java.util.ArrayList;
12 +import java.util.Arrays;
13 +import java.util.Collections;
14 +import java.util.Comparator;
15 +import java.util.List;
16 +
17 +import org.junit.Before;
18 +import org.junit.Test;
19 +import org.onlab.onos.net.ConnectPoint;
20 +import org.onlab.onos.net.DefaultHost;
21 +import org.onlab.onos.net.Device;
22 +import org.onlab.onos.net.DeviceId;
23 +import org.onlab.onos.net.Host;
24 +import org.onlab.onos.net.HostId;
25 +import org.onlab.onos.net.HostLocation;
26 +import org.onlab.onos.net.Link;
27 +import org.onlab.onos.net.Port;
28 +import org.onlab.onos.net.PortNumber;
29 +import org.onlab.onos.net.device.DeviceListener;
30 +import org.onlab.onos.net.device.DeviceService;
31 +import org.onlab.onos.net.flow.instructions.Instruction;
32 +import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
33 +import org.onlab.onos.net.host.HostService;
34 +import org.onlab.onos.net.link.LinkListener;
35 +import org.onlab.onos.net.link.LinkService;
36 +import org.onlab.onos.net.packet.OutboundPacket;
37 +import org.onlab.onos.net.packet.PacketProcessor;
38 +import org.onlab.onos.net.packet.PacketService;
39 +import org.onlab.onos.net.provider.ProviderId;
40 +import org.onlab.packet.ARP;
41 +import org.onlab.packet.Ethernet;
42 +import org.onlab.packet.IpPrefix;
43 +import org.onlab.packet.MacAddress;
44 +import org.onlab.packet.VlanId;
45 +
46 +import com.google.common.collect.Sets;
47 +
48 +/**
49 + * Tests for the {@link ProxyArpManager} class.
50 + */
51 +public class ProxyArpManagerTest {
52 +
53 + private static final int NUM_DEVICES = 4;
54 + private static final int NUM_PORTS_PER_DEVICE = 3;
55 + private static final int NUM_FLOOD_PORTS = 4;
56 +
57 + private static final IpPrefix IP1 = IpPrefix.valueOf("10.0.0.1/24");
58 + private static final IpPrefix IP2 = IpPrefix.valueOf("10.0.0.2/24");
59 +
60 + private static final ProviderId PID = new ProviderId("of", "foo");
61 +
62 + private static final VlanId VLAN1 = VlanId.vlanId((short) 1);
63 + private static final VlanId VLAN2 = VlanId.vlanId((short) 2);
64 + private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01");
65 + private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
66 + private static final HostId HID1 = HostId.hostId(MAC1, VLAN1);
67 + private static final HostId HID2 = HostId.hostId(MAC2, VLAN1);
68 +
69 + private static final DeviceId DID1 = getDeviceId(1);
70 + private static final DeviceId DID2 = getDeviceId(2);
71 + private static final PortNumber P1 = PortNumber.portNumber(1);
72 + private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L);
73 + private static final HostLocation LOC2 = new HostLocation(DID2, P1, 123L);
74 +
75 + private ProxyArpManager proxyArp;
76 +
77 + private TestPacketService packetService;
78 +
79 + private DeviceService deviceService;
80 + private LinkService linkService;
81 + private HostService hostService;
82 +
83 + @Before
84 + public void setUp() throws Exception {
85 + proxyArp = new ProxyArpManager();
86 + packetService = new TestPacketService();
87 + proxyArp.packetService = packetService;
88 +
89 + // Create a host service mock here. Must be replayed by tests once the
90 + // expectations have been set up
91 + hostService = createMock(HostService.class);
92 + proxyArp.hostService = hostService;
93 +
94 + createTopology();
95 + proxyArp.deviceService = deviceService;
96 + proxyArp.linkService = linkService;
97 +
98 + proxyArp.activate();
99 + }
100 +
101 + /**
102 + * Creates a fake topology to feed into the ARP module.
103 + * <p/>
104 + * The default topology is a unidirectional ring topology. Each switch has
105 + * 3 ports. Ports 2 and 3 have the links to neighbor switches, and port 1
106 + * is free (edge port).
107 + */
108 + private void createTopology() {
109 + deviceService = createMock(DeviceService.class);
110 + linkService = createMock(LinkService.class);
111 +
112 + deviceService.addListener(anyObject(DeviceListener.class));
113 + linkService.addListener(anyObject(LinkListener.class));
114 +
115 + createDevices(NUM_DEVICES, NUM_PORTS_PER_DEVICE);
116 + createLinks(NUM_DEVICES);
117 + }
118 +
119 + /**
120 + * Creates the devices for the fake topology.
121 + */
122 + private void createDevices(int numDevices, int numPorts) {
123 + List<Device> devices = new ArrayList<>();
124 +
125 + for (int i = 1; i <= numDevices; i++) {
126 + DeviceId devId = getDeviceId(i);
127 + Device device = createMock(Device.class);
128 + expect(device.id()).andReturn(devId).anyTimes();
129 + replay(device);
130 +
131 + devices.add(device);
132 +
133 + List<Port> ports = new ArrayList<>();
134 + for (int j = 1; j <= numPorts; j++) {
135 + Port port = createMock(Port.class);
136 + expect(port.number()).andReturn(PortNumber.portNumber(j)).anyTimes();
137 + replay(port);
138 + ports.add(port);
139 + }
140 +
141 + expect(deviceService.getPorts(devId)).andReturn(ports);
142 + }
143 +
144 + expect(deviceService.getDevices()).andReturn(devices);
145 + replay(deviceService);
146 + }
147 +
148 + /**
149 + * Creates the links for the fake topology.
150 + * NB: Only unidirectional links are created, as for this purpose all we
151 + * need is to occupy the ports with some link.
152 + */
153 + private void createLinks(int numDevices) {
154 + List<Link> links = new ArrayList<Link>();
155 +
156 + for (int i = 1; i <= numDevices; i++) {
157 + ConnectPoint src = new ConnectPoint(
158 + getDeviceId(i),
159 + PortNumber.portNumber(2));
160 + ConnectPoint dst = new ConnectPoint(
161 + getDeviceId((i + 1 > numDevices) ? 1 : i + 1),
162 + PortNumber.portNumber(3));
163 +
164 + Link link = createMock(Link.class);
165 + expect(link.src()).andReturn(src).anyTimes();
166 + expect(link.dst()).andReturn(dst).anyTimes();
167 + replay(link);
168 +
169 + links.add(link);
170 + }
171 +
172 + expect(linkService.getLinks()).andReturn(links).anyTimes();
173 + replay(linkService);
174 + }
175 +
176 + /**
177 + * Tests {@link ProxyArpManager#known(IpPrefix)} in the case where the
178 + * IP address is not known.
179 + * Verifies the method returns false.
180 + */
181 + @Test
182 + public void testNotKnown() {
183 + expect(hostService.getHostsByIp(IP1)).andReturn(Collections.<Host>emptySet());
184 + replay(hostService);
185 +
186 + assertFalse(proxyArp.known(IP1));
187 + }
188 +
189 + /**
190 + * Tests {@link ProxyArpManager#known(IpPrefix)} in the case where the
191 + * IP address is known.
192 + * Verifies the method returns true.
193 + */
194 + @Test
195 + public void testKnown() {
196 + Host host1 = createMock(Host.class);
197 + Host host2 = createMock(Host.class);
198 +
199 + expect(hostService.getHostsByIp(IP1))
200 + .andReturn(Sets.newHashSet(host1, host2));
201 + replay(hostService);
202 +
203 + assertTrue(proxyArp.known(IP1));
204 + }
205 +
206 + /**
207 + * Tests {@link ProxyArpManager#reply(Ethernet)} in the case where the
208 + * destination host is known.
209 + * Verifies the correct ARP reply is sent out the correct port.
210 + */
211 + @Test
212 + public void testReplyKnown() {
213 + Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC2,
214 + Collections.singleton(IP1));
215 +
216 + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
217 + Collections.singleton(IP2));
218 +
219 + expect(hostService.getHostsByIp(IpPrefix.valueOf(IP1.toOctets())))
220 + .andReturn(Collections.singleton(replyer));
221 + expect(hostService.getHost(HID2)).andReturn(requestor);
222 +
223 + replay(hostService);
224 +
225 + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1);
226 +
227 + proxyArp.reply(arpRequest);
228 +
229 + assertEquals(1, packetService.packets.size());
230 + Ethernet arpReply = buildArp(ARP.OP_REPLY, MAC1, MAC2, IP1, IP2);
231 + verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
232 + }
233 +
234 + /**
235 + * Tests {@link ProxyArpManager#reply(Ethernet)} in the case where the
236 + * destination host is not known.
237 + * Verifies the ARP request is flooded out the correct edge ports.
238 + */
239 + @Test
240 + public void testReplyUnknown() {
241 + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
242 + Collections.singleton(IP2));
243 +
244 + expect(hostService.getHostsByIp(IpPrefix.valueOf(IP1.toOctets())))
245 + .andReturn(Collections.<Host>emptySet());
246 + expect(hostService.getHost(HID2)).andReturn(requestor);
247 +
248 + replay(hostService);
249 +
250 + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1);
251 +
252 + proxyArp.reply(arpRequest);
253 +
254 + verifyFlood(arpRequest);
255 + }
256 +
257 + /**
258 + * Tests {@link ProxyArpManager#reply(Ethernet)} in the case where the
259 + * destination host is known for that IP address, but is not on the same
260 + * VLAN as the source host.
261 + * Verifies the ARP request is flooded out the correct edge ports.
262 + */
263 + @Test
264 + public void testReplyDifferentVlan() {
265 + Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN2, LOC2,
266 + Collections.singleton(IP1));
267 +
268 + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
269 + Collections.singleton(IP2));
270 +
271 + expect(hostService.getHostsByIp(IpPrefix.valueOf(IP1.toOctets())))
272 + .andReturn(Collections.singleton(replyer));
273 + expect(hostService.getHost(HID2)).andReturn(requestor);
274 +
275 + replay(hostService);
276 +
277 + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1);
278 +
279 + proxyArp.reply(arpRequest);
280 +
281 + verifyFlood(arpRequest);
282 + }
283 +
284 + /**
285 + * Tests {@link ProxyArpManager#forward(Ethernet)} in the case where the
286 + * destination host is known.
287 + * Verifies the correct ARP request is sent out the correct port.
288 + */
289 + @Test
290 + public void testForwardToHost() {
291 + Host host1 = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1,
292 + Collections.singleton(IP1));
293 +
294 + expect(hostService.getHost(HID1)).andReturn(host1);
295 + replay(hostService);
296 +
297 + Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1);
298 +
299 + proxyArp.forward(arpRequest);
300 +
301 + assertEquals(1, packetService.packets.size());
302 + OutboundPacket packet = packetService.packets.get(0);
303 +
304 + verifyPacketOut(arpRequest, LOC1, packet);
305 + }
306 +
307 + /**
308 + * Tests {@link ProxyArpManager#forward(Ethernet)} in the case where the
309 + * destination host is not known.
310 + * Verifies the correct ARP request is flooded out the correct edge ports.
311 + */
312 + @Test
313 + public void testForwardFlood() {
314 + expect(hostService.getHost(HID1)).andReturn(null);
315 + replay(hostService);
316 +
317 + Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1);
318 +
319 + proxyArp.forward(arpRequest);
320 +
321 + verifyFlood(arpRequest);
322 + }
323 +
324 + /**
325 + * Verifies that the given packet was flooded out all available edge ports.
326 + *
327 + * @param packet the packet that was expected to be flooded
328 + */
329 + private void verifyFlood(Ethernet packet) {
330 + assertEquals(NUM_FLOOD_PORTS, packetService.packets.size());
331 +
332 + Collections.sort(packetService.packets,
333 + new Comparator<OutboundPacket>() {
334 + @Override
335 + public int compare(OutboundPacket o1, OutboundPacket o2) {
336 + return o1.sendThrough().uri().compareTo(o2.sendThrough().uri());
337 + }
338 + });
339 +
340 + for (int i = 0; i < NUM_FLOOD_PORTS; i++) {
341 + ConnectPoint cp = new ConnectPoint(getDeviceId(i + 1), PortNumber.portNumber(1));
342 +
343 + OutboundPacket outboundPacket = packetService.packets.get(i);
344 + verifyPacketOut(packet, cp, outboundPacket);
345 + }
346 + }
347 +
348 + /**
349 + * Verifies the given packet was sent out the given port.
350 + *
351 + * @param expected the packet that was expected to be sent
352 + * @param outPort the port the packet was expected to be sent out
353 + * @param actual the actual OutboundPacket to verify
354 + */
355 + private void verifyPacketOut(Ethernet expected, ConnectPoint outPort,
356 + OutboundPacket actual) {
357 + assertTrue(Arrays.equals(expected.serialize(), actual.data().array()));
358 + assertEquals(1, actual.treatment().instructions().size());
359 + assertEquals(outPort.deviceId(), actual.sendThrough());
360 + Instruction instruction = actual.treatment().instructions().get(0);
361 + assertTrue(instruction instanceof OutputInstruction);
362 + assertEquals(outPort.port(), ((OutputInstruction) instruction).port());
363 + }
364 +
365 + /**
366 + * Returns the device ID of the ith device.
367 + *
368 + * @param i device to get the ID of
369 + * @return the device ID
370 + */
371 + private static DeviceId getDeviceId(int i) {
372 + return DeviceId.deviceId("" + i);
373 + }
374 +
375 + /**
376 + * Builds an ARP packet with the given parameters.
377 + *
378 + * @param opcode opcode of the ARP packet
379 + * @param srcMac source MAC address
380 + * @param dstMac destination MAC address, or null if this is a request
381 + * @param srcIp source IP address
382 + * @param dstIp destination IP address
383 + * @return the ARP packet
384 + */
385 + private Ethernet buildArp(short opcode, MacAddress srcMac, MacAddress dstMac,
386 + IpPrefix srcIp, IpPrefix dstIp) {
387 + Ethernet eth = new Ethernet();
388 +
389 + if (dstMac == null) {
390 + eth.setDestinationMACAddress(MacAddress.BROADCAST_MAC);
391 + } else {
392 + eth.setDestinationMACAddress(dstMac.getAddress());
393 + }
394 +
395 + eth.setSourceMACAddress(srcMac.getAddress());
396 + eth.setEtherType(Ethernet.TYPE_ARP);
397 + eth.setVlanID(VLAN1.toShort());
398 +
399 + ARP arp = new ARP();
400 + arp.setOpCode(opcode);
401 + arp.setProtocolType(ARP.PROTO_TYPE_IP);
402 + arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
403 +
404 + arp.setProtocolAddressLength((byte) IpPrefix.INET_LEN);
405 + arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
406 + arp.setSenderHardwareAddress(srcMac.getAddress());
407 +
408 + if (dstMac == null) {
409 + arp.setTargetHardwareAddress(MacAddress.ZERO_MAC_ADDRESS);
410 + } else {
411 + arp.setTargetHardwareAddress(dstMac.getAddress());
412 + }
413 +
414 + arp.setSenderProtocolAddress(srcIp.toOctets());
415 + arp.setTargetProtocolAddress(dstIp.toOctets());
416 +
417 + eth.setPayload(arp);
418 + return eth;
419 + }
420 +
421 + /**
422 + * Test PacketService implementation that simply stores OutboundPackets
423 + * passed to {@link #emit(OutboundPacket)} for later verification.
424 + */
425 + class TestPacketService implements PacketService {
426 +
427 + List<OutboundPacket> packets = new ArrayList<>();
428 +
429 + @Override
430 + public void addProcessor(PacketProcessor processor, int priority) {
431 + }
432 +
433 + @Override
434 + public void removeProcessor(PacketProcessor processor) {
435 + }
436 +
437 + @Override
438 + public void emit(OutboundPacket packet) {
439 + packets.add(packet);
440 + }
441 + }
442 +}