blob: 3532abc79c36e6b9c5cf8d1ad2a1aef1af7954f9 [file] [log] [blame]
Jonathan Hartaa380972014-04-03 10:24:46 -07001package net.onrc.onos.core.intent.runtime;
Ray Milkey6688cd82014-03-11 16:40:46 -07002
Jonathan Harta88fd242014-04-03 11:24:54 -07003import static org.easymock.EasyMock.anyObject;
4import static org.easymock.EasyMock.createMock;
5import static org.easymock.EasyMock.eq;
6import static org.easymock.EasyMock.expect;
7import static org.easymock.EasyMock.expectLastCall;
8import static org.easymock.EasyMock.replay;
9import static org.easymock.EasyMock.verify;
10import static org.hamcrest.MatcherAssert.assertThat;
11import static org.hamcrest.Matchers.equalTo;
12import static org.hamcrest.Matchers.hasItem;
13import static org.hamcrest.Matchers.hasSize;
14import static org.hamcrest.Matchers.notNullValue;
15
16import java.util.Collection;
17import java.util.LinkedList;
18import java.util.List;
19
Ray Milkey6688cd82014-03-11 16:40:46 -070020import net.floodlightcontroller.core.module.FloodlightModuleContext;
21import net.floodlightcontroller.core.module.FloodlightModuleException;
Jonathan Hart6df90172014-04-03 10:13:11 -070022import net.onrc.onos.core.datagrid.IDatagridService;
23import net.onrc.onos.core.datagrid.IEventChannel;
24import net.onrc.onos.core.datagrid.IEventChannelListener;
Jonathan Hartaa380972014-04-03 10:24:46 -070025import net.onrc.onos.core.intent.Intent;
Jonathan Harta88fd242014-04-03 11:24:54 -070026import net.onrc.onos.core.intent.Intent.IntentState;
Jonathan Hartaa380972014-04-03 10:24:46 -070027import net.onrc.onos.core.intent.IntentMap;
Jonathan Harta88fd242014-04-03 11:24:54 -070028import net.onrc.onos.core.intent.IntentOperation.Operator;
Jonathan Hartaa380972014-04-03 10:24:46 -070029import net.onrc.onos.core.intent.IntentOperationList;
30import net.onrc.onos.core.intent.MockNetworkGraph;
31import net.onrc.onos.core.intent.ShortestPathIntent;
Jonathan Hartdeda0ba2014-04-03 11:14:12 -070032import net.onrc.onos.core.registry.IControllerRegistryService;
Jonathan Hart472062d2014-04-03 10:56:48 -070033import net.onrc.onos.core.topology.DeviceEvent;
34import net.onrc.onos.core.topology.INetworkGraphListener;
35import net.onrc.onos.core.topology.INetworkGraphService;
36import net.onrc.onos.core.topology.LinkEvent;
37import net.onrc.onos.core.topology.PortEvent;
38import net.onrc.onos.core.topology.SwitchEvent;
Ray Milkey6688cd82014-03-11 16:40:46 -070039
Jonathan Harta88fd242014-04-03 11:24:54 -070040import org.hamcrest.Description;
41import org.hamcrest.Factory;
42import org.hamcrest.Matcher;
43import org.hamcrest.Matchers;
44import org.hamcrest.TypeSafeMatcher;
Ray Milkey6688cd82014-03-11 16:40:46 -070045import org.junit.After;
46import org.junit.Before;
47import org.junit.Test;
48import org.junit.runner.RunWith;
49import org.powermock.api.easymock.PowerMock;
50import org.powermock.core.classloader.annotations.PrepareForTest;
51import org.powermock.modules.junit4.PowerMockRunner;
52
Ray Milkey6688cd82014-03-11 16:40:46 -070053/**
54 * @author Ray Milkey (ray@onlab.us)
Ray Milkeydc659c42014-03-28 16:30:42 -070055 *
56 * Unit tests for the Path Calculation Runtime module (PathCalcRuntimeModule).
57 * These test cases check the results of creating paths, deleting paths, and
58 * rerouting paths. The network graph, controller registry, and data grid are
59 * mocked out. The individual tests check the high level intents and the
60 * resulting operation lists to be sure they match the intended APIs.
61 *
Ray Milkey6688cd82014-03-11 16:40:46 -070062 */
63@RunWith(PowerMockRunner.class)
64@PrepareForTest(PathCalcRuntimeModule.class)
65public class PathCalcRuntimeModuleTest {
Ray Milkeydc659c42014-03-28 16:30:42 -070066 private static final Long LOCAL_PORT = 0xFFFEL;
67
Ray Milkey6688cd82014-03-11 16:40:46 -070068 private FloodlightModuleContext modContext;
69 private IDatagridService datagridService;
70 private INetworkGraphService networkGraphService;
71 private IControllerRegistryService controllerRegistryService;
72 private PersistIntent persistIntent;
Ray Milkeydc659c42014-03-28 16:30:42 -070073 private MockNetworkGraph graph;
Ray Milkey6688cd82014-03-11 16:40:46 -070074
75 @SuppressWarnings("unchecked")
76 @Before
77 public void setUp() throws Exception {
Ray Milkeydc659c42014-03-28 16:30:42 -070078 graph = new MockNetworkGraph();
Ray Milkey6688cd82014-03-11 16:40:46 -070079 graph.createSampleTopology1();
80
81 datagridService = createMock(IDatagridService.class);
82 networkGraphService = createMock(INetworkGraphService.class);
83 controllerRegistryService = createMock(IControllerRegistryService.class);
84 modContext = createMock(FloodlightModuleContext.class);
85 final IEventChannel eventChannel = createMock(IEventChannel.class);
86 persistIntent = PowerMock.createMock(PersistIntent.class);
87
88 PowerMock.expectNew(PersistIntent.class,
89 anyObject(IControllerRegistryService.class),
90 anyObject(INetworkGraphService.class)).andReturn(persistIntent);
91
92 expect(modContext.getServiceImpl(IDatagridService.class))
93 .andReturn(datagridService).once();
94 expect(modContext.getServiceImpl(INetworkGraphService.class))
95 .andReturn(networkGraphService).once();
96 expect(modContext.getServiceImpl(IControllerRegistryService.class))
97 .andReturn(controllerRegistryService).once();
98 expect(persistIntent.getKey()).andReturn(1L).anyTimes();
99 expect(persistIntent.persistIfLeader(eq(1L),
100 anyObject(IntentOperationList.class))).andReturn(true)
101 .anyTimes();
102
103 expect(networkGraphService.getNetworkGraph()).andReturn(graph)
104 .anyTimes();
105 networkGraphService.registerNetworkGraphListener(
106 anyObject(INetworkGraphListener.class));
107 expectLastCall();
108
109 expect(datagridService.createChannel("onos.pathintent",
110 Long.class, IntentOperationList.class))
111 .andReturn(eventChannel).once();
112
113 expect(datagridService.addListener(
114 eq("onos.pathintent_state"),
115 anyObject(IEventChannelListener.class),
116 eq(Long.class),
117 eq(IntentStateList.class)))
118 .andReturn(eventChannel).once();
119
120 replay(datagridService);
121 replay(networkGraphService);
122 replay(modContext);
123 replay(controllerRegistryService);
124 PowerMock.replay(persistIntent, PersistIntent.class);
125 }
126
Ray Milkeydc659c42014-03-28 16:30:42 -0700127
128 /**
129 * Hamcrest matcher to check that a collection of Intents contains an
130 * Intent with the specified Intent Id.
131 */
132 public static class EntryForIntentMatcher extends TypeSafeMatcher<Collection<Intent>> {
133 final private String id;
134
135 public EntryForIntentMatcher(String idValue) {
136 id = idValue;
137 }
138
139 @Override
140 public boolean matchesSafely(Collection<Intent> intents) {
141 assertThat(intents,
142 hasItem(Matchers.<Intent>hasProperty("id", equalTo(id))));
143 return true;
144 }
145
146 @Override
147 public void describeTo(Description description) {
148 description.appendText("an intent with id \" ").
149 appendText(id).
150 appendText("\"");
151 }
152 }
153
154
155 /**
156 * Factory method to create an Intent entry Matcher. Returns a matcher
157 * for the Intent with the given id.
158 * @param id id of the intent to match
159 * @return Matcher object
160 */
161 @Factory
162 public static Matcher<Collection<Intent>> hasIntentWithId(String id) {
163 return new EntryForIntentMatcher(id);
164 }
165
166
167 /**
168 * Matcher to determine if an IntentMap contains an entry with a given id,
169 * and that entry has a given state.
170 */
171 public static class IntentsHaveIntentWithStateMatcher extends TypeSafeMatcher<IntentMap> {
172 final private String id;
173 final private IntentState state;
174 private Intent intent;
175
176 public IntentsHaveIntentWithStateMatcher(String idValue,
177 IntentState stateValue) {
178 id = idValue;
179 state = stateValue;
180 }
181
182 @Override
183 public boolean matchesSafely(IntentMap intents) {
184 intent = intents.getIntent(id);
185
186 return intent != null && intent.getState() == state;
187 }
188
189 @Override
190 public void describeTo(Description description) {
191 if (intent == null) {
192 description.appendText("intent lookup for id \"");
193 description.appendText(id);
194 description.appendText("\"");
195 } else {
196 description.appendText("state ");
197 description.appendText(state.toString());
198 }
199 }
200
201 @Override
202 public void describeMismatchSafely(IntentMap intents,
203 Description mismatchDescription) {
204 if (intent != null) {
205 mismatchDescription.appendText("was ").
206 appendText(intent.getState().toString());
207 } else {
208 mismatchDescription.appendText("that intent was not found");
209 }
210 }
211 }
212
213
214 /**
215 * Factory method to create a Matcher for an IntentMap that looks for an
216 * Intent with a given id and state.
217 *
218 * @param id id of the Intent to match
219 * @param state if the Intent is found, its state must match this
220 * @return Matcher object
221 */
222 @Factory
223 public static Matcher<IntentMap> hasIntentWithIdAndState(String id,
224 IntentState state) {
225 return new IntentsHaveIntentWithStateMatcher(id, state);
226 }
227
228
Ray Milkey6688cd82014-03-11 16:40:46 -0700229 @After
230 public void tearDown() {
231 verify(datagridService);
232 verify(networkGraphService);
233 verify(modContext);
234 verify(controllerRegistryService);
235 PowerMock.verify(persistIntent, PersistIntent.class);
236 }
237
238 /**
239 * Test the result of executing a path calculation on an
240 * Intent Operation List which contains a path that references a
241 * non-existent switch.
Ray Milkeydc659c42014-03-28 16:30:42 -0700242 *
Ray Milkey6688cd82014-03-11 16:40:46 -0700243 * A 3 path list is created where one of the paths references a switch
244 * that is not in the topology. The test checks that the resulting
245 * Operation List has entries for the 2 correct paths, and that the
246 * high level intents contain a proper error entry for the bad path.
247 */
248 @Test
249 public void testInvalidSwitchName() throws FloodlightModuleException {
Ray Milkey6688cd82014-03-11 16:40:46 -0700250 final String BAD_SWITCH_INTENT_NAME = "No Such Switch Intent";
251
252 // create shortest path intents
253 final IntentOperationList opList = new IntentOperationList();
254 opList.add(Operator.ADD,
255 new ShortestPathIntent(BAD_SWITCH_INTENT_NAME, 111L, 12L,
256 LOCAL_PORT, 2L, 21L, LOCAL_PORT));
257 opList.add(Operator.ADD,
258 new ShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L,
259 LOCAL_PORT));
260 opList.add(Operator.ADD,
261 new ShortestPathIntent("3", 2L, 23L, LOCAL_PORT, 3L, 32L,
262 LOCAL_PORT));
263
264 // compile high-level intent operations into low-level intent
265 // operations (calculate paths)
266 final PathCalcRuntimeModule runtime = new PathCalcRuntimeModule();
267 runtime.init(modContext);
268 runtime.startUp(modContext);
269 final IntentOperationList pathIntentOpList =
270 runtime.executeIntentOperations(opList);
271 assertThat(pathIntentOpList, notNullValue());
272
273 final IntentMap highLevelIntents = runtime.getHighLevelIntents();
274 assertThat(highLevelIntents, notNullValue());
275
276 final Collection<Intent> allIntents = highLevelIntents.getAllIntents();
277 assertThat(allIntents, notNullValue());
278
279 // One intent had an error and should not create a path list entry
280 assertThat(pathIntentOpList, hasSize(opList.size() - 1));
281
282 // Should be a high level intent for each operation
Ray Milkeydc659c42014-03-28 16:30:42 -0700283 assertThat(allIntents, hasSize(opList.size()));
Ray Milkey6688cd82014-03-11 16:40:46 -0700284
285 // Check that we got a high level intent for each operation
Ray Milkeydc659c42014-03-28 16:30:42 -0700286 assertThat(allIntents, hasIntentWithId("3"));
287 assertThat(allIntents, hasIntentWithId("2"));
288 assertThat(allIntents, hasIntentWithId(BAD_SWITCH_INTENT_NAME));
Ray Milkey6688cd82014-03-11 16:40:46 -0700289
290 // Check that the non existent switch was NACKed
Ray Milkeydc659c42014-03-28 16:30:42 -0700291 assertThat(highLevelIntents, hasIntentWithIdAndState(BAD_SWITCH_INTENT_NAME, IntentState.INST_NACK));
Ray Milkey6688cd82014-03-11 16:40:46 -0700292
293 // Check that switch 2 was correctly processed
Ray Milkeydc659c42014-03-28 16:30:42 -0700294 assertThat(highLevelIntents, hasIntentWithIdAndState("2", IntentState.INST_REQ));
Ray Milkey6688cd82014-03-11 16:40:46 -0700295
296 // Check that switch 3 was correctly processed
Ray Milkeydc659c42014-03-28 16:30:42 -0700297 assertThat(highLevelIntents, hasIntentWithIdAndState("3", IntentState.INST_REQ));
298
299 }
300
301
302 /**
303 * Test the result of executing a path calculation on an
304 * Intent Operation List and then removing one of the switches.
305 *
306 * A 3 path list is created and then one of the paths is removed.
307 * The test checks that the resulting Operation List is correct,
308 * and that the high level intents contain a proper "delete requested"
309 * entry for the deleted path.
310 */
311 @Test
312 public void testIntentRemoval() throws FloodlightModuleException {
313
314 // create shortest path intents
315 final IntentOperationList opList = new IntentOperationList();
316 opList.add(Operator.ADD,
317 new ShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L,
318 LOCAL_PORT));
319 opList.add(Operator.ADD,
320 new ShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L,
321 LOCAL_PORT));
322 opList.add(Operator.ADD,
323 new ShortestPathIntent("3", 2L, 23L, LOCAL_PORT, 3L, 32L,
324 LOCAL_PORT));
325
326 // compile high-level intent operations into low-level intent
327 // operations (calculate paths)
328 final PathCalcRuntimeModule runtime = new PathCalcRuntimeModule();
329 runtime.init(modContext);
330 runtime.startUp(modContext);
331 final IntentOperationList pathIntentOpList =
332 runtime.executeIntentOperations(opList);
333 assertThat(pathIntentOpList, notNullValue());
334
335 final IntentMap highLevelIntents = runtime.getHighLevelIntents();
336 assertThat(highLevelIntents, notNullValue());
337
338 final Collection<Intent> allIntents = highLevelIntents.getAllIntents();
339 assertThat(allIntents, notNullValue());
340
341 // Should be one operation per path
342 assertThat(pathIntentOpList, hasSize(opList.size()));
343
344 // Should be a high level intent for each operation
345 assertThat(allIntents, hasSize(opList.size()));
346
347 // Check that we got a high level intent for each operation
348 assertThat(allIntents, hasIntentWithId("3"));
349 assertThat(allIntents, hasIntentWithId("2"));
350 assertThat(allIntents, hasIntentWithId("1"));
351
352 // Check that switch 1 was correctly processed
353 assertThat(highLevelIntents,
354 hasIntentWithIdAndState("1", IntentState.INST_REQ));
355
356 // Check that switch 2 was correctly processed
357 assertThat(highLevelIntents,
358 hasIntentWithIdAndState("2", IntentState.INST_REQ));
359
360 // Check that switch 3 was correctly processed
361 assertThat(highLevelIntents,
362 hasIntentWithIdAndState("3", IntentState.INST_REQ));
363
364 // Now delete one path and check the results
365 final IntentOperationList opListForRemoval = new IntentOperationList();
366 opListForRemoval.add(Operator.REMOVE,
367 new ShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L,
368 LOCAL_PORT));
369
370 final IntentOperationList pathIntentOpListAfterRemoval =
371 runtime.executeIntentOperations(opListForRemoval);
372 assertThat(pathIntentOpListAfterRemoval, notNullValue());
373 assertThat(pathIntentOpListAfterRemoval, hasSize(1));
374
375 // Check the high level intents.
376 final IntentMap highLevelIntentsAfterRemoval = runtime.getHighLevelIntents();
377 assertThat(highLevelIntentsAfterRemoval, notNullValue());
378
379 final Collection<Intent> allIntentsAfterRemoval = highLevelIntentsAfterRemoval.getAllIntents();
380 assertThat(allIntentsAfterRemoval, notNullValue());
381 assertThat(allIntentsAfterRemoval, hasSize(3));
382
383 // Check that we got a high level intent for each operation
384 assertThat(allIntentsAfterRemoval, hasIntentWithId("3"));
385 assertThat(allIntentsAfterRemoval, hasIntentWithId("2"));
386 assertThat(allIntentsAfterRemoval, hasIntentWithId("1"));
387
388 // Check the states of the high level intents
389 // Check that switch 1 was correctly processed
390 assertThat(highLevelIntents,
391 hasIntentWithIdAndState("1", IntentState.DEL_REQ));
392
393 // Check that switch 2 was correctly processed
394 assertThat(highLevelIntents,
395 hasIntentWithIdAndState("2", IntentState.INST_REQ));
396
397 // Check that switch 3 was correctly processed
398 assertThat(highLevelIntents,
399 hasIntentWithIdAndState("3", IntentState.INST_REQ));
400 }
401
402 /**
403 * Test the result of executing a path calculation on an
404 * Intent Operation List and then forcing a reroute.
405 *
406 * A 3 path list is created and then one of the links is removed.
407 * The test checks that the resulting Operation List is correct,
408 * and that the high level intents contain a proper "reroute requested"
409 * entry for the deleted link.
410 */
411 @Test
412 public void testIntentReroute() throws FloodlightModuleException {
413
414 // create shortest path intents
415 final IntentOperationList opList = new IntentOperationList();
416 final ShortestPathIntent pathIntent1 =
417 new ShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L,
418 LOCAL_PORT);
419
420 opList.add(Operator.ADD, pathIntent1);
421 opList.add(Operator.ADD,
422 new ShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L,
423 LOCAL_PORT));
424 opList.add(Operator.ADD,
425 new ShortestPathIntent("3", 2L, 23L, LOCAL_PORT, 3L, 32L,
426 LOCAL_PORT));
427
428 // compile high-level intent operations into low-level intent
429 // operations (calculate paths)
430 final PathCalcRuntimeModule runtime = new PathCalcRuntimeModule();
431 runtime.init(modContext);
432 runtime.startUp(modContext);
433 final IntentOperationList pathIntentOpList =
434 runtime.executeIntentOperations(opList);
435 assertThat(pathIntentOpList, notNullValue());
436
437 final IntentMap highLevelIntents = runtime.getHighLevelIntents();
438 assertThat(highLevelIntents, notNullValue());
439
440 final Collection<Intent> allIntents = highLevelIntents.getAllIntents();
441 assertThat(allIntents, notNullValue());
442
443 // Should be one operation per path
444 assertThat(pathIntentOpList, hasSize(opList.size()));
445
446 // Should be a high level intent for each operation
447 assertThat(allIntents, hasSize(opList.size()));
448
449 // Check that we got a high level intent for each operation
450 assertThat(allIntents, hasIntentWithId("3"));
451 assertThat(allIntents, hasIntentWithId("2"));
452 assertThat(allIntents, hasIntentWithId("1"));
453
454 // Check that switch 1 was correctly processed
455 assertThat(highLevelIntents,
456 hasIntentWithIdAndState("1", IntentState.INST_REQ));
457
458 // Check that switch 2 was correctly processed
459 assertThat(highLevelIntents,
460 hasIntentWithIdAndState("2", IntentState.INST_REQ));
461
462 // Check that switch 3 was correctly processed
463 assertThat(highLevelIntents,
464 hasIntentWithIdAndState("3", IntentState.INST_REQ));
465
466 // Now add a different path to one of the switches path and check
467 // the results
468 IntentStateList states = new IntentStateList();
469 states.put("1", IntentState.INST_ACK);
470 states.put("2", IntentState.INST_ACK);
471 states.put("3", IntentState.INST_ACK);
472 runtime.getHighLevelIntents().changeStates(states);
473 states.clear();
474 states.put("1___0", IntentState.INST_ACK);
475 states.put("2___0", IntentState.INST_ACK);
476 states.put("3___0", IntentState.INST_ACK);
477 runtime.getPathIntents().changeStates(states);
478
479 final List<SwitchEvent> emptySwitchEvents = new LinkedList<>();
480 final List<PortEvent> emptyPortEvents = new LinkedList<>();
481 final List<DeviceEvent> emptyDeviceEvents = new LinkedList<>();
482 final List<LinkEvent> addedLinkEvents = new LinkedList<>();
483 final List<LinkEvent> removedLinkEvents = new LinkedList<>();
484
485 graph.removeLink(1L, 12L, 2L, 21L); // This link is used by the intent "1"
486 graph.removeLink(2L, 21L, 1L, 12L);
487 LinkEvent linkEvent1 = new LinkEvent(1L, 12L, 2L, 21L);
488 LinkEvent linkEvent2 = new LinkEvent(2L, 21L, 1L, 12L);
489 removedLinkEvents.add(linkEvent1);
490 removedLinkEvents.add(linkEvent2);
491 runtime.networkGraphEvents(
492 emptySwitchEvents,
493 emptySwitchEvents,
494 emptyPortEvents,
495 emptyPortEvents,
496 addedLinkEvents,
497 removedLinkEvents,
498 emptyDeviceEvents,
499 emptyDeviceEvents);
500 final IntentOperationList opListForReroute = new IntentOperationList();
501 opListForReroute.add(Operator.ADD, pathIntent1);
502
503 final IntentOperationList pathIntentOpListAfterReroute =
504 runtime.executeIntentOperations(opListForReroute);
505 assertThat(pathIntentOpListAfterReroute, notNullValue());
506 assertThat(pathIntentOpListAfterReroute, hasSize(2));
507
508 // Check the high level intents.
509 final IntentMap highLevelIntentsAfterReroute = runtime.getHighLevelIntents();
510 assertThat(highLevelIntentsAfterReroute, notNullValue());
511
512 final Collection<Intent> allIntentsAfterReroute = highLevelIntentsAfterReroute.getAllIntents();
513 assertThat(allIntentsAfterReroute, notNullValue());
514 assertThat(allIntentsAfterReroute, hasSize(3));
515
516 // Check that we got a high level intent for each operation
517 assertThat(allIntentsAfterReroute, hasIntentWithId("3"));
518 assertThat(allIntentsAfterReroute, hasIntentWithId("2"));
519 assertThat(allIntentsAfterReroute, hasIntentWithId("1"));
520
521 // Check the states of the high level intents
522 // Check that switch 1 was correctly processed
523 assertThat(highLevelIntents,
524 hasIntentWithIdAndState("1", IntentState.REROUTE_REQ));
525
526 // Check that switch 2 was correctly processed
527 assertThat(highLevelIntents,
528 hasIntentWithIdAndState("2", IntentState.INST_ACK));
529
530 // Check that switch 3 was correctly processed
531 assertThat(highLevelIntents,
532 hasIntentWithIdAndState("3", IntentState.INST_ACK));
533
Ray Milkey6688cd82014-03-11 16:40:46 -0700534
535 }
536
537
538}