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
Showing
3 changed files
with
130 additions
and
25 deletions
... | @@ -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 | ... | ... |
-
Please register or login to post a comment