Umesh Krishnaswamy | 345ee99 | 2012-12-13 20:29:48 -0800 | [diff] [blame] | 1 | package net.floodlightcontroller.staticflowentry; |
| 2 | |
| 3 | import java.io.IOException; |
| 4 | import java.util.HashMap; |
| 5 | import java.util.HashSet; |
| 6 | import java.util.LinkedList; |
| 7 | import java.util.List; |
| 8 | import java.util.Map; |
| 9 | import java.util.Set; |
| 10 | |
| 11 | |
| 12 | import org.easymock.Capture; |
| 13 | import org.easymock.CaptureType; |
| 14 | import org.junit.Test; |
| 15 | import org.openflow.protocol.OFFlowMod; |
| 16 | import org.openflow.protocol.OFMatch; |
| 17 | import org.openflow.protocol.OFMessage; |
| 18 | import org.openflow.protocol.OFPort; |
| 19 | import org.openflow.protocol.action.OFAction; |
| 20 | import org.openflow.protocol.action.OFActionOutput; |
| 21 | import org.openflow.util.HexString; |
| 22 | |
| 23 | |
| 24 | import net.floodlightcontroller.core.FloodlightContext; |
| 25 | import net.floodlightcontroller.core.IFloodlightProviderService.Role; |
| 26 | import net.floodlightcontroller.core.IOFSwitch; |
| 27 | import net.floodlightcontroller.core.module.FloodlightModuleContext; |
| 28 | import net.floodlightcontroller.core.module.FloodlightModuleException; |
| 29 | import net.floodlightcontroller.core.test.MockFloodlightProvider; |
| 30 | import net.floodlightcontroller.test.FloodlightTestCase; |
| 31 | import net.floodlightcontroller.restserver.RestApiServer; |
| 32 | import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher; |
| 33 | import net.floodlightcontroller.storage.IStorageSourceService; |
| 34 | import net.floodlightcontroller.storage.memory.MemoryStorageSource; |
| 35 | import static net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher.*; |
| 36 | import static org.easymock.EasyMock.*; |
| 37 | |
| 38 | public class StaticFlowTests extends FloodlightTestCase { |
| 39 | |
| 40 | static String TestSwitch1DPID = "00:00:00:00:00:00:00:01"; |
| 41 | static int TotalTestRules = 3; |
| 42 | |
| 43 | /*** |
| 44 | * Create TestRuleXXX and the corresponding FlowModXXX |
| 45 | * for X = 1..3 |
| 46 | */ |
| 47 | static Map<String,Object> TestRule1; |
| 48 | static OFFlowMod FlowMod1; |
| 49 | static { |
| 50 | FlowMod1 = new OFFlowMod(); |
| 51 | TestRule1 = new HashMap<String,Object>(); |
| 52 | TestRule1.put(COLUMN_NAME, "TestRule1"); |
| 53 | TestRule1.put(COLUMN_SWITCH, TestSwitch1DPID); |
| 54 | // setup match |
| 55 | OFMatch match = new OFMatch(); |
| 56 | TestRule1.put(COLUMN_DL_DST, "00:20:30:40:50:60"); |
| 57 | match.fromString("dl_dst=00:20:30:40:50:60"); |
| 58 | // setup actions |
| 59 | List<OFAction> actions = new LinkedList<OFAction>(); |
| 60 | TestRule1.put(COLUMN_ACTIONS, "output=1"); |
| 61 | actions.add(new OFActionOutput((short)1, (short) Short.MAX_VALUE)); |
| 62 | // done |
| 63 | FlowMod1.setMatch(match); |
| 64 | FlowMod1.setActions(actions); |
| 65 | FlowMod1.setBufferId(-1); |
| 66 | FlowMod1.setOutPort(OFPort.OFPP_NONE.getValue()); |
| 67 | FlowMod1.setPriority(Short.MAX_VALUE); |
| 68 | FlowMod1.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions |
| 69 | } |
| 70 | |
| 71 | static Map<String,Object> TestRule2; |
| 72 | static OFFlowMod FlowMod2; |
| 73 | |
| 74 | static { |
| 75 | FlowMod2 = new OFFlowMod(); |
| 76 | TestRule2 = new HashMap<String,Object>(); |
| 77 | TestRule2.put(COLUMN_NAME, "TestRule2"); |
| 78 | TestRule2.put(COLUMN_SWITCH, TestSwitch1DPID); |
| 79 | // setup match |
| 80 | OFMatch match = new OFMatch(); |
| 81 | TestRule2.put(COLUMN_NW_DST, "192.168.1.0/24"); |
| 82 | match.fromString("nw_dst=192.168.1.0/24"); |
| 83 | // setup actions |
| 84 | List<OFAction> actions = new LinkedList<OFAction>(); |
| 85 | TestRule2.put(COLUMN_ACTIONS, "output=1"); |
| 86 | actions.add(new OFActionOutput((short)1, (short) Short.MAX_VALUE)); |
| 87 | // done |
| 88 | FlowMod2.setMatch(match); |
| 89 | FlowMod2.setActions(actions); |
| 90 | FlowMod2.setBufferId(-1); |
| 91 | FlowMod2.setOutPort(OFPort.OFPP_NONE.getValue()); |
| 92 | FlowMod2.setPriority(Short.MAX_VALUE); |
| 93 | FlowMod2.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions |
| 94 | |
| 95 | } |
| 96 | |
| 97 | |
| 98 | static Map<String,Object> TestRule3; |
| 99 | static OFFlowMod FlowMod3; |
| 100 | static { |
| 101 | FlowMod3 = new OFFlowMod(); |
| 102 | TestRule3 = new HashMap<String,Object>(); |
| 103 | TestRule3.put(COLUMN_NAME, "TestRule3"); |
| 104 | TestRule3.put(COLUMN_SWITCH, TestSwitch1DPID); |
| 105 | // setup match |
| 106 | OFMatch match = new OFMatch(); |
| 107 | TestRule3.put(COLUMN_DL_DST, "00:20:30:40:50:60"); |
| 108 | TestRule3.put(COLUMN_DL_VLAN, 4096); |
| 109 | match.fromString("dl_dst=00:20:30:40:50:60,dl_vlan=4096"); |
| 110 | // setup actions |
| 111 | TestRule3.put(COLUMN_ACTIONS, "output=controller"); |
| 112 | List<OFAction> actions = new LinkedList<OFAction>(); |
| 113 | actions.add(new OFActionOutput(OFPort.OFPP_CONTROLLER.getValue(), (short) Short.MAX_VALUE)); |
| 114 | // done |
| 115 | FlowMod3.setMatch(match); |
| 116 | FlowMod3.setActions(actions); |
| 117 | FlowMod3.setBufferId(-1); |
| 118 | FlowMod3.setOutPort(OFPort.OFPP_NONE.getValue()); |
| 119 | FlowMod3.setPriority(Short.MAX_VALUE); |
| 120 | FlowMod3.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions |
| 121 | |
| 122 | } |
| 123 | |
| 124 | private void verifyFlowMod(OFFlowMod testFlowMod, |
| 125 | OFFlowMod goodFlowMod) { |
| 126 | verifyMatch(testFlowMod, goodFlowMod); |
| 127 | verifyActions(testFlowMod, goodFlowMod); |
| 128 | // dont' bother testing the cookie; just copy it over |
| 129 | goodFlowMod.setCookie(testFlowMod.getCookie()); |
| 130 | // .. so we can continue to use .equals() |
| 131 | assertEquals(goodFlowMod, testFlowMod); |
| 132 | } |
| 133 | |
| 134 | |
| 135 | private void verifyMatch(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) { |
| 136 | assertEquals(goodFlowMod.getMatch(), testFlowMod.getMatch()); |
| 137 | } |
| 138 | |
| 139 | |
| 140 | private void verifyActions(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) { |
| 141 | List<OFAction> goodActions = goodFlowMod.getActions(); |
| 142 | List<OFAction> testActions = testFlowMod.getActions(); |
| 143 | assertNotNull(goodActions); |
| 144 | assertNotNull(testActions); |
| 145 | assertEquals(goodActions.size(), testActions.size()); |
| 146 | // assumes actions are marshalled in same order; should be safe |
| 147 | for(int i = 0; i < goodActions.size(); i++) { |
| 148 | assertEquals(goodActions.get(i), testActions.get(i)); |
| 149 | } |
| 150 | |
| 151 | } |
| 152 | |
| 153 | |
| 154 | @Override |
| 155 | public void setUp() throws Exception { |
| 156 | super.setUp(); |
| 157 | } |
| 158 | |
| 159 | @Test |
| 160 | public void testStaticFlowPush() throws IOException { |
| 161 | StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher(); |
| 162 | IStorageSourceService storage = createStorageWithFlowEntries(); |
| 163 | long dpid = HexString.toLong(TestSwitch1DPID); |
| 164 | |
| 165 | // Create a Switch and attach a switch |
| 166 | IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class); |
| 167 | Capture<OFMessage> writeCapture = new Capture<OFMessage>(CaptureType.ALL); |
| 168 | Capture<FloodlightContext> contextCapture = new Capture<FloodlightContext>(CaptureType.ALL); |
| 169 | Capture<List<OFMessage>> writeCaptureList = new Capture<List<OFMessage>>(CaptureType.ALL); |
| 170 | |
| 171 | //OFMessageSafeOutStream mockOutStream = createNiceMock(OFMessageSafeOutStream.class); |
| 172 | mockSwitch.write(capture(writeCapture), capture(contextCapture)); |
| 173 | expectLastCall().anyTimes(); |
| 174 | mockSwitch.write(capture(writeCaptureList), capture(contextCapture)); |
| 175 | expectLastCall().anyTimes(); |
| 176 | mockSwitch.flush(); |
| 177 | expectLastCall().anyTimes(); |
| 178 | |
| 179 | staticFlowEntryPusher.setStorageSource(storage); |
| 180 | |
| 181 | FloodlightModuleContext fmc = new FloodlightModuleContext(); |
| 182 | |
| 183 | MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider(); |
| 184 | Map<Long, IOFSwitch> switchMap = new HashMap<Long, IOFSwitch>(); |
| 185 | switchMap.put(dpid, mockSwitch); |
| 186 | // NO ! expect(mockFloodlightProvider.getSwitches()).andReturn(switchMap).anyTimes(); |
| 187 | mockFloodlightProvider.setSwitches(switchMap); |
| 188 | staticFlowEntryPusher.setFloodlightProvider(mockFloodlightProvider); |
| 189 | RestApiServer restApi = new RestApiServer(); |
| 190 | try { |
| 191 | restApi.init(fmc); |
| 192 | } catch (FloodlightModuleException e) { |
| 193 | e.printStackTrace(); |
| 194 | } |
| 195 | staticFlowEntryPusher.restApi = restApi; |
| 196 | staticFlowEntryPusher.startUp(null); // again, to hack unittest |
| 197 | |
| 198 | // verify that flowpusher read all three entries from storage |
| 199 | assertEquals(TotalTestRules, staticFlowEntryPusher.countEntries()); |
| 200 | |
| 201 | // if someone calls mockSwitch.getOutputStream(), return mockOutStream instead |
| 202 | //expect(mockSwitch.getOutputStream()).andReturn(mockOutStream).anyTimes(); |
| 203 | |
| 204 | // if someone calls getId(), return this dpid instead |
| 205 | expect(mockSwitch.getId()).andReturn(dpid).anyTimes(); |
| 206 | expect(mockSwitch.getStringId()).andReturn(TestSwitch1DPID).anyTimes(); |
| 207 | replay(mockSwitch); |
| 208 | |
| 209 | // hook the static pusher up to the fake switch |
| 210 | staticFlowEntryPusher.addedSwitch(mockSwitch); |
| 211 | |
| 212 | verify(mockSwitch); |
| 213 | |
| 214 | // Verify that the switch has gotten some flow_mods |
| 215 | assertEquals(true, writeCapture.hasCaptured()); |
| 216 | assertEquals(TotalTestRules, writeCapture.getValues().size()); |
| 217 | |
| 218 | // Order assumes how things are stored in hash bucket; |
| 219 | // should be fixed because OFMessage.hashCode() is deterministic |
| 220 | OFFlowMod firstFlowMod = (OFFlowMod) writeCapture.getValues().get(2); |
| 221 | verifyFlowMod(firstFlowMod, FlowMod1); |
| 222 | OFFlowMod secondFlowMod = (OFFlowMod) writeCapture.getValues().get(1); |
| 223 | verifyFlowMod(secondFlowMod, FlowMod2); |
| 224 | OFFlowMod thirdFlowMod = (OFFlowMod) writeCapture.getValues().get(0); |
| 225 | verifyFlowMod(thirdFlowMod, FlowMod3); |
| 226 | |
| 227 | writeCapture.reset(); |
| 228 | contextCapture.reset(); |
| 229 | |
| 230 | |
| 231 | // delete two rules and verify they've been removed |
| 232 | // this should invoke staticFlowPusher.rowsDeleted() |
| 233 | storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule1"); |
| 234 | storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule2"); |
| 235 | |
| 236 | assertEquals(1, staticFlowEntryPusher.countEntries()); |
| 237 | assertEquals(2, writeCapture.getValues().size()); |
| 238 | |
| 239 | OFFlowMod firstDelete = (OFFlowMod) writeCapture.getValues().get(0); |
| 240 | FlowMod1.setCommand(OFFlowMod.OFPFC_DELETE_STRICT); |
| 241 | verifyFlowMod(firstDelete, FlowMod1); |
| 242 | |
| 243 | OFFlowMod secondDelete = (OFFlowMod) writeCapture.getValues().get(1); |
| 244 | FlowMod2.setCommand(OFFlowMod.OFPFC_DELETE_STRICT); |
| 245 | verifyFlowMod(secondDelete, FlowMod2); |
| 246 | |
| 247 | // add rules back to make sure that staticFlowPusher.rowsInserted() works |
| 248 | writeCapture.reset(); |
| 249 | FlowMod2.setCommand(OFFlowMod.OFPFC_ADD); |
| 250 | storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2); |
| 251 | assertEquals(2, staticFlowEntryPusher.countEntries()); |
| 252 | assertEquals(1, writeCaptureList.getValues().size()); |
| 253 | List<OFMessage> outList = |
| 254 | (List<OFMessage>) writeCaptureList.getValues().get(0); |
| 255 | assertEquals(1, outList.size()); |
| 256 | OFFlowMod firstAdd = (OFFlowMod) outList.get(0); |
| 257 | verifyFlowMod(firstAdd, FlowMod2); |
| 258 | writeCapture.reset(); |
| 259 | contextCapture.reset(); |
| 260 | writeCaptureList.reset(); |
| 261 | |
| 262 | // now try an update, calling staticFlowPusher.rowUpdated() |
| 263 | TestRule3.put(COLUMN_DL_VLAN, 333); |
| 264 | storage.updateRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3); |
| 265 | assertEquals(2, staticFlowEntryPusher.countEntries()); |
| 266 | assertEquals(1, writeCaptureList.getValues().size()); |
| 267 | |
| 268 | outList = (List<OFMessage>) writeCaptureList.getValues().get(0); |
| 269 | assertEquals(2, outList.size()); |
| 270 | OFFlowMod removeFlowMod = (OFFlowMod) outList.get(0); |
| 271 | FlowMod3.setCommand(OFFlowMod.OFPFC_DELETE_STRICT); |
| 272 | verifyFlowMod(removeFlowMod, FlowMod3); |
| 273 | FlowMod3.setCommand(OFFlowMod.OFPFC_ADD); |
| 274 | FlowMod3.getMatch().fromString("dl_dst=00:20:30:40:50:60,dl_vlan=333"); |
| 275 | OFFlowMod updateFlowMod = (OFFlowMod) outList.get(1); |
| 276 | verifyFlowMod(updateFlowMod, FlowMod3); |
| 277 | |
| 278 | } |
| 279 | |
| 280 | |
| 281 | IStorageSourceService createStorageWithFlowEntries() { |
| 282 | return populateStorageWithFlowEntries(new MemoryStorageSource()); |
| 283 | } |
| 284 | |
| 285 | IStorageSourceService populateStorageWithFlowEntries(IStorageSourceService storage) { |
| 286 | Set<String> indexedColumns = new HashSet<String>(); |
| 287 | indexedColumns.add(COLUMN_NAME); |
| 288 | storage.createTable(StaticFlowEntryPusher.TABLE_NAME, indexedColumns); |
| 289 | storage.setTablePrimaryKeyName(StaticFlowEntryPusher.TABLE_NAME, COLUMN_NAME); |
| 290 | |
| 291 | storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule1); |
| 292 | storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2); |
| 293 | storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3); |
| 294 | |
| 295 | return storage; |
| 296 | } |
| 297 | |
| 298 | @Test |
| 299 | public void testHARoleChanged() throws IOException { |
| 300 | StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher(); |
| 301 | IStorageSourceService storage = createStorageWithFlowEntries(); |
| 302 | MockFloodlightProvider mfp = getMockFloodlightProvider(); |
| 303 | staticFlowEntryPusher.setFloodlightProvider(mfp); |
| 304 | staticFlowEntryPusher.setStorageSource(storage); |
| 305 | RestApiServer restApi = new RestApiServer(); |
| 306 | try { |
| 307 | FloodlightModuleContext fmc = new FloodlightModuleContext(); |
| 308 | restApi.init(fmc); |
| 309 | } catch (FloodlightModuleException e) { |
| 310 | e.printStackTrace(); |
| 311 | } |
| 312 | staticFlowEntryPusher.restApi = restApi; |
| 313 | staticFlowEntryPusher.startUp(null); // again, to hack unittest |
| 314 | |
| 315 | assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID)); |
| 316 | assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1)); |
| 317 | assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2)); |
| 318 | assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3)); |
| 319 | |
| 320 | // Send a notification that we've changed to slave |
| 321 | mfp.dispatchRoleChanged(null, Role.SLAVE); |
| 322 | // Make sure we've removed all our entries |
| 323 | assert(staticFlowEntryPusher.entry2dpid.isEmpty()); |
| 324 | assert(staticFlowEntryPusher.entriesFromStorage.isEmpty()); |
| 325 | |
| 326 | // Send a notification that we've changed to master |
| 327 | mfp.dispatchRoleChanged(Role.SLAVE, Role.MASTER); |
| 328 | // Make sure we've learned the entries |
| 329 | assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID)); |
| 330 | assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1)); |
| 331 | assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2)); |
| 332 | assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3)); |
| 333 | } |
| 334 | } |