Ray Milkey
Committed by Gerrit Code Review

Unit tests and manager changes for failure cases

Add unit tests for intent intallation failures:
- compilation error
- installation exception
- installation returns error future

Change-Id: Idc5e19980032cc5c1b9804c7627833fb126b7a81
...@@ -300,6 +300,7 @@ public class IntentManager ...@@ -300,6 +300,7 @@ public class IntentManager
300 update.setInstallables(installables); 300 update.setInstallables(installables);
301 } catch (PathNotFoundException e) { 301 } catch (PathNotFoundException e) {
302 log.debug("Path not found for intent {}", intent); 302 log.debug("Path not found for intent {}", intent);
303 + update.setInflightState(intent, FAILED);
303 } catch (IntentException e) { 304 } catch (IntentException e) {
304 log.warn("Unable to compile intent {} due to:", intent.id(), e); 305 log.warn("Unable to compile intent {} due to:", intent.id(), e);
305 306
...@@ -350,11 +351,12 @@ public class IntentManager ...@@ -350,11 +351,12 @@ public class IntentManager
350 installable.resources()); 351 installable.resources());
351 try { 352 try {
352 batches.addAll(getInstaller(installable).install(installable)); 353 batches.addAll(getInstaller(installable).install(installable));
353 - } catch (IntentException e) { 354 + } catch (Exception e) { // TODO this should be IntentException
354 log.warn("Unable to install intent {} due to:", update.newIntent().id(), e); 355 log.warn("Unable to install intent {} due to:", update.newIntent().id(), e);
355 trackerService.removeTrackedResources(update.newIntent().id(), 356 trackerService.removeTrackedResources(update.newIntent().id(),
356 installable.resources()); 357 installable.resources());
357 - //FIXME we failed... intent should be recompiled 358 + //TODO we failed; intent should be recompiled
359 + update.setInflightState(update.newIntent(), FAILED);
358 } 360 }
359 } 361 }
360 update.addBatches(batches); 362 update.addBatches(batches);
...@@ -660,11 +662,9 @@ public class IntentManager ...@@ -660,11 +662,9 @@ public class IntentManager
660 return !isComplete() ? batches.get(currentBatch) : null; 662 return !isComplete() ? batches.get(currentBatch) : null;
661 } 663 }
662 664
663 - void batchSuccess(BatchWrite batchWrite) { 665 + void batchSuccess() {
664 // move on to next Batch 666 // move on to next Batch
665 - if (++currentBatch == batches.size()) { 667 + currentBatch++;
666 - finalizeStates(batchWrite);
667 - }
668 } 668 }
669 669
670 void batchFailed() { 670 void batchFailed() {
...@@ -684,7 +684,6 @@ public class IntentManager ...@@ -684,7 +684,6 @@ public class IntentManager
684 // TODO we might want to try to recompile the new intent 684 // TODO we might want to try to recompile the new intent
685 } 685 }
686 686
687 - // make sure this is called!!!
688 private void finalizeStates(BatchWrite batchWrite) { 687 private void finalizeStates(BatchWrite batchWrite) {
689 // events to be triggered on successful write 688 // events to be triggered on successful write
690 for (Intent intent : stateMap.keySet()) { 689 for (Intent intent : stateMap.keySet()) {
...@@ -699,6 +698,7 @@ public class IntentManager ...@@ -699,6 +698,7 @@ public class IntentManager
699 batchWrite.removeIntent(intent.id()); 698 batchWrite.removeIntent(intent.id());
700 break; 699 break;
701 case FAILED: 700 case FAILED:
701 + batchWrite.setState(intent, FAILED);
702 batchWrite.removeInstalledIntents(intent.id()); 702 batchWrite.removeInstalledIntents(intent.id());
703 break; 703 break;
704 704
...@@ -801,27 +801,39 @@ public class IntentManager ...@@ -801,27 +801,39 @@ public class IntentManager
801 batch.addAll(update.currentBatch()); 801 batch.addAll(update.currentBatch());
802 } 802 }
803 } 803 }
804 - return (batch.size() > 0) ? flowRuleService.applyBatch(batch) : null; 804 + if (batch.size() > 0) {
805 - } 805 + //FIXME apply batch might throw an exception
806 - 806 + return flowRuleService.applyBatch(batch);
807 - private void updateBatches(CompletedBatchOperation completed) { 807 + } else {
808 - if (completed.isSuccess()) { 808 + // there are no flow rule batches; finalize the intent update
809 BatchWrite batchWrite = store.newBatchWrite(); 809 BatchWrite batchWrite = store.newBatchWrite();
810 for (IntentUpdate update : intentUpdates) { 810 for (IntentUpdate update : intentUpdates) {
811 - update.batchSuccess(batchWrite); 811 + update.finalizeStates(batchWrite);
812 } 812 }
813 if (!batchWrite.isEmpty()) { 813 if (!batchWrite.isEmpty()) {
814 store.batchWrite(batchWrite); 814 store.batchWrite(batchWrite);
815 } 815 }
816 + return null;
817 + }
818 + }
819 +
820 + private void updateBatches(CompletedBatchOperation completed) {
821 + if (completed.isSuccess()) {
822 + for (IntentUpdate update : intentUpdates) {
823 + update.batchSuccess();
824 + }
816 } else { 825 } else {
817 // entire batch has been reverted... 826 // entire batch has been reverted...
818 - log.warn("Failed items: {}", completed.failedItems()); 827 + log.debug("Failed items: {}", completed.failedItems());
828 + log.debug("Failed ids: {}", completed.failedIds());
819 829
820 for (Long id : completed.failedIds()) { 830 for (Long id : completed.failedIds()) {
821 IntentId targetId = IntentId.valueOf(id); 831 IntentId targetId = IntentId.valueOf(id);
822 for (IntentUpdate update : intentUpdates) { 832 for (IntentUpdate update : intentUpdates) {
823 List<Intent> installables = Lists.newArrayList(update.newInstallables()); 833 List<Intent> installables = Lists.newArrayList(update.newInstallables());
824 - installables.addAll(update.oldInstallables()); 834 + if (update.oldInstallables() != null) {
835 + installables.addAll(update.oldInstallables());
836 + }
825 for (Intent intent : installables) { 837 for (Intent intent : installables) {
826 if (intent.id().equals(targetId)) { 838 if (intent.id().equals(targetId)) {
827 update.batchFailed(); 839 update.batchFailed();
...@@ -837,8 +849,9 @@ public class IntentManager ...@@ -837,8 +849,9 @@ public class IntentManager
837 private void abandonShip() { 849 private void abandonShip() {
838 // the batch has failed 850 // the batch has failed
839 // TODO: maybe we should do more? 851 // TODO: maybe we should do more?
840 - future = null;
841 log.error("Walk the plank, matey..."); 852 log.error("Walk the plank, matey...");
853 + future = null;
854 + batchService.removeIntentOperations(ops);
842 } 855 }
843 856
844 /** 857 /**
...@@ -866,11 +879,15 @@ public class IntentManager ...@@ -866,11 +879,15 @@ public class IntentManager
866 if (future.cancel(true)) { // cancel success; batch is reverted 879 if (future.cancel(true)) { // cancel success; batch is reverted
867 // reset the timer 880 // reset the timer
868 resetTimeoutLimit(); 881 resetTimeoutLimit();
869 - if (installAttempt++ >= MAX_ATTEMPTS) { 882 + installAttempt++;
883 + if (installAttempt == MAX_ATTEMPTS) {
870 log.warn("Install request timed out: {}", ops); 884 log.warn("Install request timed out: {}", ops);
871 for (IntentUpdate update : intentUpdates) { 885 for (IntentUpdate update : intentUpdates) {
872 update.batchFailed(); 886 update.batchFailed();
873 } 887 }
888 + } else if (installAttempt > MAX_ATTEMPTS) {
889 + abandonShip();
890 + return;
874 } // else just resubmit the work 891 } // else just resubmit the work
875 future = applyNextBatch(); 892 future = applyNextBatch();
876 executor.submit(this); 893 executor.submit(this);
...@@ -880,7 +897,7 @@ public class IntentManager ...@@ -880,7 +897,7 @@ public class IntentManager
880 // cancel failed... batch is broken; shouldn't happen! 897 // cancel failed... batch is broken; shouldn't happen!
881 // we could manually reverse everything 898 // we could manually reverse everything
882 // ... or just core dump and send email to Ali 899 // ... or just core dump and send email to Ali
883 - batchService.removeIntentOperations(ops); 900 + abandonShip();
884 } 901 }
885 } 902 }
886 903
...@@ -925,6 +942,7 @@ public class IntentManager ...@@ -925,6 +942,7 @@ public class IntentManager
925 log.error("Error submitting batches:", e); 942 log.error("Error submitting batches:", e);
926 // FIXME incomplete Intents should be cleaned up 943 // FIXME incomplete Intents should be cleaned up
927 // (transition to FAILED, etc.) 944 // (transition to FAILED, etc.)
945 + abandonShip();
928 } 946 }
929 } 947 }
930 } 948 }
......
...@@ -206,6 +206,56 @@ public class IntentManagerTest { ...@@ -206,6 +206,56 @@ public class IntentManagerTest {
206 flowRuleService.flows.iterator().next().priority()); 206 flowRuleService.flows.iterator().next().priority());
207 } 207 }
208 208
209 + /**
210 + * Tests for proper behavior of installation of an intent that triggers
211 + * a compilation error.
212 + */
213 + @Test
214 + public void errorIntentCompile() {
215 + final TestIntentCompilerError errorCompiler = new TestIntentCompilerError();
216 + extensionService.registerCompiler(MockIntent.class, errorCompiler);
217 + MockIntent intent = new MockIntent(MockIntent.nextId());
218 + listener.setLatch(1, Type.INSTALL_REQ);
219 + listener.setLatch(1, Type.FAILED);
220 + service.submit(intent);
221 + listener.await(Type.INSTALL_REQ);
222 + listener.await(Type.FAILED);
223 + }
224 +
225 + /**
226 + * Tests handling a future that contains an error as a result of
227 + * installing an intent.
228 + */
229 + @Test
230 + public void errorIntentInstallFromFlows() {
231 + final Long id = MockIntent.nextId();
232 + flowRuleService.setFuture(false, 1);
233 + MockIntent intent = new MockIntent(id);
234 + listener.setLatch(1, Type.FAILED);
235 + listener.setLatch(1, Type.INSTALL_REQ);
236 + service.submit(intent);
237 + listener.await(Type.INSTALL_REQ);
238 + delay(10); // need to make sure we have some failed futures returned first
239 + flowRuleService.setFuture(true, 0);
240 + listener.await(Type.FAILED);
241 + }
242 +
243 + /**
244 + * Tests handling of an error that is generated by the intent installer.
245 + */
246 + @Test
247 + public void errorIntentInstallFromInstaller() {
248 + final TestIntentErrorInstaller errorInstaller = new TestIntentErrorInstaller();
249 + extensionService.registerInstaller(MockInstallableIntent.class, errorInstaller);
250 + MockIntent intent = new MockIntent(MockIntent.nextId());
251 + listener.setLatch(1, Type.INSTALL_REQ);
252 + listener.setLatch(1, Type.FAILED);
253 + service.submit(intent);
254 + listener.await(Type.INSTALL_REQ);
255 + listener.await(Type.FAILED);
256 +
257 + }
258 +
209 private static class TestListener implements IntentListener { 259 private static class TestListener implements IntentListener {
210 final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create(); 260 final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create();
211 Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap(); 261 Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap();
...@@ -229,7 +279,7 @@ public class IntentManagerTest { ...@@ -229,7 +279,7 @@ public class IntentManagerTest {
229 public void await(IntentEvent.Type type) { 279 public void await(IntentEvent.Type type) {
230 try { 280 try {
231 assertTrue("Timed out waiting for: " + type, 281 assertTrue("Timed out waiting for: " + type,
232 - latchMap.get(type).await(5, TimeUnit.SECONDS)); 282 + latchMap.get(type).await(5, TimeUnit.SECONDS));
233 } catch (InterruptedException e) { 283 } catch (InterruptedException e) {
234 e.printStackTrace(); 284 e.printStackTrace();
235 } 285 }
...@@ -299,6 +349,14 @@ public class IntentManagerTest { ...@@ -299,6 +349,14 @@ public class IntentManagerTest {
299 } 349 }
300 } 350 }
301 351
352 + private static class TestIntentCompilerError implements IntentCompiler<MockIntent> {
353 + @Override
354 + public List<Intent> compile(MockIntent intent, List<Intent> installable,
355 + Set<LinkResourceAllocations> resources) {
356 + throw new IntentCompilationException("Compilation always fails");
357 + }
358 + }
359 +
302 private static class TestIntentInstaller implements IntentInstaller<MockInstallableIntent> { 360 private static class TestIntentInstaller implements IntentInstaller<MockInstallableIntent> {
303 @Override 361 @Override
304 public List<FlowRuleBatchOperation> install(MockInstallableIntent intent) { 362 public List<FlowRuleBatchOperation> install(MockInstallableIntent intent) {
...@@ -327,6 +385,23 @@ public class IntentManagerTest { ...@@ -327,6 +385,23 @@ public class IntentManagerTest {
327 } 385 }
328 } 386 }
329 387
388 + private static class TestIntentErrorInstaller implements IntentInstaller<MockInstallableIntent> {
389 + @Override
390 + public List<FlowRuleBatchOperation> install(MockInstallableIntent intent) {
391 + throw new IntentInstallationException("install() always fails");
392 + }
393 +
394 + @Override
395 + public List<FlowRuleBatchOperation> uninstall(MockInstallableIntent intent) {
396 + throw new IntentRemovalException("uninstall() always fails");
397 + }
398 +
399 + @Override
400 + public List<FlowRuleBatchOperation> replace(MockInstallableIntent oldIntent, MockInstallableIntent newIntent) {
401 + throw new IntentInstallationException("replace() always fails");
402 + }
403 + }
404 +
330 /** 405 /**
331 * Hamcrest matcher to check that a conllection of Intents contains an 406 * Hamcrest matcher to check that a conllection of Intents contains an
332 * Intent with the specified Intent Id. 407 * Intent with the specified Intent Id.
......
1 package org.onlab.onos.net.intent.impl; 1 package org.onlab.onos.net.intent.impl;
2 2
3 -import com.google.common.collect.Sets; 3 +import java.util.Collections;
4 -import com.google.common.util.concurrent.Futures; 4 +import java.util.Set;
5 +import java.util.concurrent.Future;
6 +
5 import org.onlab.onos.core.ApplicationId; 7 import org.onlab.onos.core.ApplicationId;
6 import org.onlab.onos.net.DeviceId; 8 import org.onlab.onos.net.DeviceId;
7 import org.onlab.onos.net.flow.CompletedBatchOperation; 9 import org.onlab.onos.net.flow.CompletedBatchOperation;
...@@ -12,9 +14,9 @@ import org.onlab.onos.net.flow.FlowRuleBatchOperation; ...@@ -12,9 +14,9 @@ import org.onlab.onos.net.flow.FlowRuleBatchOperation;
12 import org.onlab.onos.net.flow.FlowRuleListener; 14 import org.onlab.onos.net.flow.FlowRuleListener;
13 import org.onlab.onos.net.flow.FlowRuleService; 15 import org.onlab.onos.net.flow.FlowRuleService;
14 16
15 -import java.util.Collections; 17 +import com.google.common.collect.ImmutableSet;
16 -import java.util.Set; 18 +import com.google.common.collect.Sets;
17 -import java.util.concurrent.Future; 19 +import com.google.common.util.concurrent.Futures;
18 20
19 21
20 public class MockFlowRuleService implements FlowRuleService { 22 public class MockFlowRuleService implements FlowRuleService {
...@@ -23,7 +25,17 @@ public class MockFlowRuleService implements FlowRuleService { ...@@ -23,7 +25,17 @@ public class MockFlowRuleService implements FlowRuleService {
23 final Set<FlowRule> flows = Sets.newHashSet(); 25 final Set<FlowRule> flows = Sets.newHashSet();
24 26
25 public void setFuture(boolean success) { 27 public void setFuture(boolean success) {
26 - future = Futures.immediateFuture(new CompletedBatchOperation(true, Collections.emptySet())); 28 + setFuture(success, 0);
29 + }
30 +
31 + public void setFuture(boolean success, long intentId) {
32 + if (success) {
33 + future = Futures.immediateFuture(new CompletedBatchOperation(true, Collections.emptySet()));
34 + } else {
35 + final Set<Long> failedIds = ImmutableSet.of(intentId);
36 + future = Futures.immediateFuture(
37 + new CompletedBatchOperation(false, flows, failedIds));
38 + }
27 } 39 }
28 40
29 @Override 41 @Override
......