Sho SHIMIZU
Committed by Ray Milkey

Refactor IntentManager: apply state pattern for intent state transition

Resolve ONOS-471.
- Define IntentUpdate sub-classes for intent state transition
- Define CompletedIntentUpdate and its sub-classes for parking intent state
- IntentUpdate.execute() handles one state transition and generates next state
- IntentInstall monitor is splitted into IntentBatchPreprocess and its sub-classes

Change-Id: Ie2d3a0b2ce9af7b98fd19a3a8cc00ab152ab6eaa
...@@ -52,12 +52,13 @@ import org.onosproject.net.intent.BatchWrite; ...@@ -52,12 +52,13 @@ import org.onosproject.net.intent.BatchWrite;
52 import org.onosproject.net.intent.IntentStoreDelegate; 52 import org.onosproject.net.intent.IntentStoreDelegate;
53 import org.slf4j.Logger; 53 import org.slf4j.Logger;
54 54
55 -import java.time.Duration;
56 import java.util.ArrayList; 55 import java.util.ArrayList;
57 import java.util.Collections; 56 import java.util.Collections;
58 import java.util.EnumSet; 57 import java.util.EnumSet;
58 +import java.util.LinkedList;
59 import java.util.List; 59 import java.util.List;
60 import java.util.Map; 60 import java.util.Map;
61 +import java.util.Optional;
61 import java.util.concurrent.ConcurrentHashMap; 62 import java.util.concurrent.ConcurrentHashMap;
62 import java.util.concurrent.ConcurrentMap; 63 import java.util.concurrent.ConcurrentMap;
63 import java.util.concurrent.ExecutionException; 64 import java.util.concurrent.ExecutionException;
...@@ -65,9 +66,10 @@ import java.util.concurrent.ExecutorService; ...@@ -65,9 +66,10 @@ import java.util.concurrent.ExecutorService;
65 import java.util.concurrent.Future; 66 import java.util.concurrent.Future;
66 import java.util.concurrent.TimeUnit; 67 import java.util.concurrent.TimeUnit;
67 import java.util.concurrent.TimeoutException; 68 import java.util.concurrent.TimeoutException;
69 +import java.util.stream.Collectors;
68 70
69 -import static com.google.common.base.Preconditions.checkArgument;
70 import static com.google.common.base.Preconditions.checkNotNull; 71 import static com.google.common.base.Preconditions.checkNotNull;
72 +import static com.google.common.base.Preconditions.checkState;
71 import static java.util.concurrent.Executors.newFixedThreadPool; 73 import static java.util.concurrent.Executors.newFixedThreadPool;
72 import static org.onosproject.net.intent.IntentState.*; 74 import static org.onosproject.net.intent.IntentState.*;
73 import static org.onlab.util.Tools.namedThreads; 75 import static org.onlab.util.Tools.namedThreads;
...@@ -155,14 +157,14 @@ public class IntentManager ...@@ -155,14 +157,14 @@ public class IntentManager
155 public void submit(Intent intent) { 157 public void submit(Intent intent) {
156 checkNotNull(intent, INTENT_NULL); 158 checkNotNull(intent, INTENT_NULL);
157 execute(IntentOperations.builder(intent.appId()) 159 execute(IntentOperations.builder(intent.appId())
158 - .addSubmitOperation(intent).build()); 160 + .addSubmitOperation(intent).build());
159 } 161 }
160 162
161 @Override 163 @Override
162 public void withdraw(Intent intent) { 164 public void withdraw(Intent intent) {
163 checkNotNull(intent, INTENT_NULL); 165 checkNotNull(intent, INTENT_NULL);
164 execute(IntentOperations.builder(intent.appId()) 166 execute(IntentOperations.builder(intent.appId())
165 - .addWithdrawOperation(intent.id()).build()); 167 + .addWithdrawOperation(intent.id()).build());
166 } 168 }
167 169
168 @Override 170 @Override
...@@ -283,100 +285,26 @@ public class IntentManager ...@@ -283,100 +285,26 @@ public class IntentManager
283 } 285 }
284 286
285 /** 287 /**
286 - * Compiles the specified intent.
287 - *
288 - * @param update intent update
289 - */
290 - private void executeCompilingPhase(IntentUpdate update) {
291 - Intent intent = update.newIntent();
292 - // Indicate that the intent is entering the compiling phase.
293 - update.setInflightState(intent, COMPILING);
294 -
295 - try {
296 - // Compile the intent into installable derivatives.
297 - List<Intent> installables = compileIntent(intent, update);
298 -
299 - // If all went well, associate the resulting list of installable
300 - // intents with the top-level intent and proceed to install.
301 - update.setInstallables(installables);
302 - } catch (PathNotFoundException e) {
303 - log.debug("Path not found for intent {}", intent);
304 - update.setInflightState(intent, FAILED);
305 - } catch (IntentException e) {
306 - log.warn("Unable to compile intent {} due to:", intent.id(), e);
307 -
308 - // If compilation failed, mark the intent as failed.
309 - update.setInflightState(intent, FAILED);
310 - }
311 - }
312 -
313 - /**
314 * Compiles an intent recursively. 288 * Compiles an intent recursively.
315 * 289 *
316 * @param intent intent 290 * @param intent intent
317 * @return result of compilation 291 * @return result of compilation
318 */ 292 */
319 - private List<Intent> compileIntent(Intent intent, IntentUpdate update) { 293 + private List<Intent> compileIntent(Intent intent, List<Intent> previousInstallables) {
320 if (intent.isInstallable()) { 294 if (intent.isInstallable()) {
321 return ImmutableList.of(intent); 295 return ImmutableList.of(intent);
322 } 296 }
323 297
324 registerSubclassCompilerIfNeeded(intent); 298 registerSubclassCompilerIfNeeded(intent);
325 - List<Intent> previous = update.oldInstallables();
326 // FIXME: get previous resources 299 // FIXME: get previous resources
327 List<Intent> installable = new ArrayList<>(); 300 List<Intent> installable = new ArrayList<>();
328 - for (Intent compiled : getCompiler(intent).compile(intent, previous, null)) { 301 + for (Intent compiled : getCompiler(intent).compile(intent, previousInstallables, null)) {
329 - installable.addAll(compileIntent(compiled, update)); 302 + installable.addAll(compileIntent(compiled, previousInstallables));
330 } 303 }
331 return installable; 304 return installable;
332 } 305 }
333 306
334 /** 307 /**
335 - * Installs all installable intents associated with the specified top-level
336 - * intent.
337 - *
338 - * @param update intent update
339 - */
340 - private void executeInstallingPhase(IntentUpdate update) {
341 - if (update.newInstallables() == null) {
342 - //no failed intents allowed past this point...
343 - return;
344 - }
345 - // Indicate that the intent is entering the installing phase.
346 - update.setInflightState(update.newIntent(), INSTALLING);
347 -
348 - List<FlowRuleBatchOperation> batches = Lists.newArrayList();
349 - for (Intent installable : update.newInstallables()) {
350 - registerSubclassInstallerIfNeeded(installable);
351 - trackerService.addTrackedResources(update.newIntent().id(),
352 - installable.resources());
353 - try {
354 - batches.addAll(getInstaller(installable).install(installable));
355 - } catch (Exception e) { // TODO this should be IntentException
356 - log.warn("Unable to install intent {} due to:", update.newIntent().id(), e);
357 - trackerService.removeTrackedResources(update.newIntent().id(),
358 - installable.resources());
359 - //TODO we failed; intent should be recompiled
360 - update.setInflightState(update.newIntent(), FAILED);
361 - }
362 - }
363 - update.addBatches(batches);
364 - }
365 -
366 - /**
367 - * Uninstalls the specified intent by uninstalling all of its associated
368 - * installable derivatives.
369 - *
370 - * @param update intent update
371 - */
372 - private void executeWithdrawingPhase(IntentUpdate update) {
373 - if (!update.oldIntent().equals(update.newIntent())) {
374 - update.setInflightState(update.oldIntent(), WITHDRAWING);
375 - } // else newIntent is FAILED
376 - update.addBatches(uninstallIntent(update.oldIntent(), update.oldInstallables()));
377 - }
378 -
379 - /**
380 * Uninstalls all installable intents associated with the given intent. 308 * Uninstalls all installable intents associated with the given intent.
381 * 309 *
382 * @param intent intent 310 * @param intent intent
...@@ -384,13 +312,10 @@ public class IntentManager ...@@ -384,13 +312,10 @@ public class IntentManager
384 * @return list of batches to uninstall intent 312 * @return list of batches to uninstall intent
385 */ 313 */
386 private List<FlowRuleBatchOperation> uninstallIntent(Intent intent, List<Intent> installables) { 314 private List<FlowRuleBatchOperation> uninstallIntent(Intent intent, List<Intent> installables) {
387 - if (installables == null) {
388 - return Collections.emptyList();
389 - }
390 List<FlowRuleBatchOperation> batches = Lists.newArrayList(); 315 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
391 for (Intent installable : installables) { 316 for (Intent installable : installables) {
392 trackerService.removeTrackedResources(intent.id(), 317 trackerService.removeTrackedResources(intent.id(),
393 - installable.resources()); 318 + installable.resources());
394 try { 319 try {
395 batches.addAll(getInstaller(installable).uninstall(installable)); 320 batches.addAll(getInstaller(installable).uninstall(installable));
396 } catch (IntentException e) { 321 } catch (IntentException e) {
...@@ -402,45 +327,6 @@ public class IntentManager ...@@ -402,45 +327,6 @@ public class IntentManager
402 } 327 }
403 328
404 /** 329 /**
405 - * Withdraws the old intent and installs the new intent as one operation.
406 - *
407 - * @param update intent update
408 - */
409 - private void executeReplacementPhase(IntentUpdate update) {
410 - checkArgument(update.oldInstallables().size() == update.newInstallables().size(),
411 - "Old and New Intent must have equivalent installable intents.");
412 - if (!update.oldIntent().equals(update.newIntent())) {
413 - // only set the old intent's state if it is different
414 - update.setInflightState(update.oldIntent(), WITHDRAWING);
415 - }
416 - update.setInflightState(update.newIntent(), INSTALLING);
417 -
418 - List<FlowRuleBatchOperation> batches = Lists.newArrayList();
419 - for (int i = 0; i < update.oldInstallables().size(); i++) {
420 - Intent oldInstallable = update.oldInstallables().get(i);
421 - Intent newInstallable = update.newInstallables().get(i);
422 - //FIXME revisit this
423 -// if (oldInstallable.equals(newInstallable)) {
424 -// continue;
425 -// }
426 - checkArgument(oldInstallable.getClass().equals(newInstallable.getClass()),
427 - "Installable Intent type mismatch.");
428 - trackerService.removeTrackedResources(update.oldIntent().id(), oldInstallable.resources());
429 - trackerService.addTrackedResources(update.newIntent().id(), newInstallable.resources());
430 - try {
431 - batches.addAll(getInstaller(newInstallable).replace(oldInstallable, newInstallable));
432 - } catch (IntentException e) {
433 - log.warn("Unable to update intent {} due to:", update.oldIntent().id(), e);
434 - //FIXME... we failed. need to uninstall (if same) or revert (if different)
435 - trackerService.removeTrackedResources(update.newIntent().id(), newInstallable.resources());
436 - update.setInflightState(update.newIntent(), FAILED);
437 - batches = uninstallIntent(update.oldIntent(), update.oldInstallables());
438 - }
439 - }
440 - update.addBatches(batches);
441 - }
442 -
443 - /**
444 * Registers an intent compiler of the specified intent if an intent compiler 330 * Registers an intent compiler of the specified intent if an intent compiler
445 * for the intent is not registered. This method traverses the class hierarchy of 331 * for the intent is not registered. This method traverses the class hierarchy of
446 * the intent. Once an intent compiler for a parent type is found, this method 332 * the intent. Once an intent compiler for a parent type is found, this method
...@@ -550,255 +436,797 @@ public class IntentManager ...@@ -550,255 +436,797 @@ public class IntentManager
550 } 436 }
551 } 437 }
552 438
553 - // TODO move this inside IntentUpdate? 439 + // TODO: simplify the branching statements
554 - /** 440 + private IntentUpdate createIntentUpdate(IntentOperation operation) {
555 - * TODO. rename this... 441 + switch (operation.type()) {
556 - * @param update intent update 442 + case SUBMIT:
557 - */ 443 + return new InstallRequest(operation.intent());
558 - private void processIntentUpdate(IntentUpdate update) { 444 + case WITHDRAW: {
559 - 445 + Intent oldIntent = store.getIntent(operation.intentId());
560 - // check to see if the intent needs to be compiled or recompiled 446 + if (oldIntent == null) {
561 - if (update.newIntent() != null) { 447 + return new DoNothing();
562 - executeCompilingPhase(update); 448 + }
563 - } 449 + List<Intent> installables = store.getInstallableIntents(oldIntent.id());
564 - 450 + if (installables == null) {
565 - if (update.oldInstallables() != null && update.newInstallables() != null) { 451 + return new WithdrawStateChange1(oldIntent);
566 - executeReplacementPhase(update); 452 + }
567 - } else if (update.newInstallables() != null) { 453 + return new WithdrawRequest(oldIntent, installables);
568 - executeInstallingPhase(update); 454 + }
569 - } else if (update.oldInstallables() != null) { 455 + case REPLACE: {
570 - executeWithdrawingPhase(update); 456 + Intent newIntent = operation.intent();
571 - } else { 457 + Intent oldIntent = store.getIntent(operation.intentId());
572 - if (update.oldIntent() != null && 458 + if (oldIntent == null) {
573 - !update.oldIntent().equals(update.newIntent())) { 459 + return new InstallRequest(newIntent);
574 - // removing failed intent 460 + }
575 - update.setInflightState(update.oldIntent(), WITHDRAWING); 461 + List<Intent> installables = store.getInstallableIntents(oldIntent.id());
576 - } 462 + if (installables == null) {
577 -// if (update.newIntent() != null) { 463 + if (newIntent.equals(oldIntent)) {
578 -// // TODO assert that next state is failed 464 + return new InstallRequest(newIntent);
579 -// } 465 + } else {
466 + return new WithdrawStateChange2(oldIntent);
467 + }
468 + }
469 + return new ReplaceRequest(newIntent, oldIntent, installables);
470 + }
471 + case UPDATE: {
472 + Intent oldIntent = store.getIntent(operation.intentId());
473 + if (oldIntent == null) {
474 + return new DoNothing();
475 + }
476 + List<Intent> installables = getInstallableIntents(oldIntent.id());
477 + if (installables == null) {
478 + return new InstallRequest(oldIntent);
479 + }
480 + return new ReplaceRequest(oldIntent, oldIntent, installables);
481 + }
482 + default:
483 + // illegal state
484 + return new DoNothing();
580 } 485 }
581 } 486 }
582 487
583 - // TODO comments... 488 + private abstract class IntentUpdate {
584 - private class IntentUpdate { 489 +
585 - private final Intent oldIntent; 490 + /**
491 + * Execute the procedure represented by the instance
492 + * and generates the next update instance.
493 + *
494 + * @return next update
495 + */
496 + public Optional<IntentUpdate> execute() {
497 + return Optional.empty();
498 + }
499 +
500 + /**
501 + * Write data to the specified BatchWrite before execution() is called.
502 + *
503 + * @param batchWrite batchWrite
504 + */
505 + public void writeBeforeExecution(BatchWrite batchWrite) {}
506 + }
507 +
508 + private abstract class CompletedIntentUpdate extends IntentUpdate {
509 +
510 + /**
511 + * Write data to the specified BatchWrite after execution() is called.
512 + *
513 + * @param batchWrite batchWrite
514 + */
515 + public void writeAfterExecution(BatchWrite batchWrite) {}
516 +
517 + public void batchSuccess() {}
518 +
519 + public void batchFailed() {}
520 +
521 + /**
522 + * Returns the current FlowRuleBatchOperation.
523 + *
524 + * @return current FlowRuleBatchOperation
525 + */
526 + public FlowRuleBatchOperation currentBatch() {
527 + return null;
528 + }
529 +
530 + /**
531 + * Returns all of installable intents this instance holds.
532 + *
533 + * @return all of installable intents
534 + */
535 + public List<Intent> allInstallables() {
536 + return Collections.emptyList();
537 + }
538 + }
539 +
540 + private class InstallRequest extends IntentUpdate {
541 +
542 + private final Intent intent;
543 +
544 + InstallRequest(Intent intent) {
545 + this.intent = checkNotNull(intent);
546 + }
547 +
548 + @Override
549 + public void writeBeforeExecution(BatchWrite batchWrite) {
550 + // TODO consider only "creating" intent if it does not exist
551 + // Note: We need to set state to INSTALL_REQ regardless.
552 + batchWrite.createIntent(intent);
553 + }
554 +
555 + @Override
556 + public Optional<IntentUpdate> execute() {
557 + return Optional.of(new Compiling(intent));
558 + }
559 + }
560 +
561 + private class WithdrawRequest extends IntentUpdate {
562 +
563 + private final Intent intent;
564 + private final List<Intent> installables;
565 +
566 + WithdrawRequest(Intent intent, List<Intent> installables) {
567 + this.intent = checkNotNull(intent);
568 + this.installables = ImmutableList.copyOf(checkNotNull(installables));
569 + }
570 +
571 + @Override
572 + public void writeBeforeExecution(BatchWrite batchWrite) {
573 + batchWrite.setState(intent, WITHDRAW_REQ);
574 + }
575 +
576 + @Override
577 + public Optional<IntentUpdate> execute() {
578 + return Optional.of(new Withdrawing(intent, installables));
579 + }
580 + }
581 +
582 + private class ReplaceRequest extends IntentUpdate {
583 +
586 private final Intent newIntent; 584 private final Intent newIntent;
587 - private final Map<Intent, IntentState> stateMap = Maps.newHashMap(); 585 + private final Intent oldIntent;
586 + private final List<Intent> oldInstallables;
587 +
588 + ReplaceRequest(Intent newIntent, Intent oldIntent, List<Intent> oldInstallables) {
589 + this.newIntent = checkNotNull(newIntent);
590 + this.oldIntent = checkNotNull(oldIntent);
591 + this.oldInstallables = ImmutableList.copyOf(oldInstallables);
592 + }
593 +
594 + @Override
595 + public void writeBeforeExecution(BatchWrite batchWrite) {
596 + // TODO consider only "creating" intent if it does not exist
597 + // Note: We need to set state to INSTALL_REQ regardless.
598 + batchWrite.createIntent(newIntent);
599 + }
600 +
601 + @Override
602 + public Optional<IntentUpdate> execute() {
603 + try {
604 + List<Intent> installables = compileIntent(newIntent, oldInstallables);
605 + return Optional.of(new Replacing(newIntent, oldIntent, installables, oldInstallables));
606 + } catch (PathNotFoundException e) {
607 + log.debug("Path not found for intent {}", newIntent);
608 + return Optional.of(new Withdrawing(oldIntent, oldInstallables));
609 + } catch (IntentException e) {
610 + log.warn("Unable to compile intent {} due to:", newIntent.id(), e);
611 + return Optional.of(new Withdrawing(oldIntent, oldInstallables));
612 + }
613 + }
614 + }
615 +
616 + private class DoNothing extends CompletedIntentUpdate {
617 + }
618 +
619 + // TODO: better naming
620 + private class WithdrawStateChange1 extends CompletedIntentUpdate {
621 +
622 + private final Intent intent;
623 +
624 + WithdrawStateChange1(Intent intent) {
625 + this.intent = checkNotNull(intent);
626 + }
627 +
628 + @Override
629 + public void writeBeforeExecution(BatchWrite batchWrite) {
630 + batchWrite.setState(intent, WITHDRAW_REQ);
631 + }
632 +
633 + @Override
634 + public void writeAfterExecution(BatchWrite batchWrite) {
635 + batchWrite.setState(intent, WITHDRAWN);
636 + batchWrite.removeInstalledIntents(intent.id());
637 + batchWrite.removeIntent(intent.id());
638 + }
639 + }
640 +
641 + // TODO: better naming
642 + private class WithdrawStateChange2 extends CompletedIntentUpdate {
643 +
644 + private final Intent intent;
645 +
646 + WithdrawStateChange2(Intent intent) {
647 + this.intent = checkNotNull(intent);
648 + }
649 +
650 + @Override
651 + public void writeBeforeExecution(BatchWrite batchWrite) {
652 + // TODO consider only "creating" intent if it does not exist
653 + // Note: We need to set state to INSTALL_REQ regardless.
654 + batchWrite.createIntent(intent);
655 + }
656 +
657 + @Override
658 + public void writeAfterExecution(BatchWrite batchWrite) {
659 + batchWrite.setState(intent, WITHDRAWN);
660 + batchWrite.removeInstalledIntents(intent.id());
661 + batchWrite.removeIntent(intent.id());
662 + }
663 + }
664 +
665 + private class Compiling extends IntentUpdate {
666 +
667 + private final Intent intent;
668 +
669 + Compiling(Intent intent) {
670 + this.intent = checkNotNull(intent);
671 + }
672 +
673 + @Override
674 + public Optional<IntentUpdate> execute() {
675 + try {
676 + // Compile the intent into installable derivatives.
677 + // If all went well, associate the resulting list of installable
678 + // intents with the top-level intent and proceed to install.
679 + return Optional.of(new Installing(intent, compileIntent(intent, null)));
680 + } catch (PathNotFoundException e) {
681 + return Optional.of(new CompilingFailed(intent, e));
682 + } catch (IntentException e) {
683 + return Optional.of(new CompilingFailed(intent, e));
684 + }
685 + }
686 + }
687 +
688 + // TODO: better naming because install() method actually generate FlowRuleBatchOperations
689 + private class Installing extends IntentUpdate {
690 +
691 + private final Intent intent;
692 + private final List<Intent> installables;
693 +
694 + Installing(Intent intent, List<Intent> installables) {
695 + this.intent = checkNotNull(intent);
696 + this.installables = ImmutableList.copyOf(checkNotNull(installables));
697 + }
698 +
699 + @Override
700 + public Optional<IntentUpdate> execute() {
701 + Exception exception = null;
702 + // Indicate that the intent is entering the installing phase.
703 +
704 + List<FlowRuleBatchOperation> batches = Lists.newArrayList();
705 + for (Intent installable : installables) {
706 + registerSubclassInstallerIfNeeded(installable);
707 + trackerService.addTrackedResources(intent.id(), installable.resources());
708 + try {
709 + batches.addAll(getInstaller(installable).install(installable));
710 + } catch (Exception e) { // TODO this should be IntentException
711 + log.warn("Unable to install intent {} due to:", intent.id(), e);
712 + trackerService.removeTrackedResources(intent.id(), installable.resources());
713 + //TODO we failed; intent should be recompiled
714 + exception = e;
715 + }
716 + }
717 +
718 + if (exception != null) {
719 + return Optional.of(new InstallingFailed(intent, installables, batches));
720 + }
721 +
722 + return Optional.of(new Installed(intent, installables, batches));
723 + }
724 + }
725 +
726 + private class Withdrawing extends IntentUpdate {
727 +
728 + private final Intent intent;
729 + private final List<Intent> installables;
730 +
731 + Withdrawing(Intent intent, List<Intent> installables) {
732 + this.intent = checkNotNull(intent);
733 + this.installables = ImmutableList.copyOf(installables);
734 + }
735 +
736 + @Override
737 + public Optional<IntentUpdate> execute() {
738 + List<FlowRuleBatchOperation> batches = uninstallIntent(intent, installables);
588 739
740 + return Optional.of(new Withdrawn(intent, installables, batches));
741 + }
742 + }
743 +
744 + private class Replacing extends IntentUpdate {
745 +
746 + private final Intent newIntent;
747 + private final Intent oldIntent;
748 + private final List<Intent> newInstallables;
589 private final List<Intent> oldInstallables; 749 private final List<Intent> oldInstallables;
590 - private List<Intent> newInstallables; 750 +
591 - private final List<FlowRuleBatchOperation> batches = Lists.newLinkedList(); 751 + private Exception exception;
592 - private int currentBatch = 0; // TODO: maybe replace with an iterator 752 +
593 - 753 + Replacing(Intent newIntent, Intent oldIntent,
594 - IntentUpdate(IntentOperation op) { 754 + List<Intent> newInstallables, List<Intent> oldInstallables) {
595 - switch (op.type()) { 755 + this.newIntent = checkNotNull(newIntent);
596 - case SUBMIT: 756 + this.oldIntent = checkNotNull(oldIntent);
597 - newIntent = op.intent(); 757 + this.newInstallables = ImmutableList.copyOf(checkNotNull(newInstallables));
598 - oldIntent = null; 758 + this.oldInstallables = ImmutableList.copyOf(checkNotNull(oldInstallables));
599 - break; 759 + }
600 - case WITHDRAW: 760 +
601 - newIntent = null; 761 + @Override
602 - oldIntent = store.getIntent(op.intentId()); 762 + public Optional<IntentUpdate> execute() {
603 - break; 763 + List<FlowRuleBatchOperation> batches = replace();
604 - case REPLACE: 764 +
605 - newIntent = op.intent(); 765 + if (exception == null) {
606 - oldIntent = store.getIntent(op.intentId()); 766 + return Optional.of(
767 + new Replaced(newIntent, oldIntent, newInstallables, oldInstallables, batches));
768 + }
769 +
770 + return Optional.of(
771 + new ReplacingFailed(newIntent, oldIntent, newInstallables, oldInstallables, batches));
772 + }
773 +
774 + protected List<FlowRuleBatchOperation> replace() {
775 + checkState(oldInstallables.size() == newInstallables.size(),
776 + "Old and New Intent must have equivalent installable intents.");
777 +
778 + List<FlowRuleBatchOperation> batches = Lists.newArrayList();
779 + for (int i = 0; i < oldInstallables.size(); i++) {
780 + Intent oldInstallable = oldInstallables.get(i);
781 + Intent newInstallable = newInstallables.get(i);
782 + //FIXME revisit this
783 +// if (oldInstallable.equals(newInstallable)) {
784 +// continue;
785 +// }
786 + checkState(oldInstallable.getClass().equals(newInstallable.getClass()),
787 + "Installable Intent type mismatch.");
788 + trackerService.removeTrackedResources(oldIntent.id(), oldInstallable.resources());
789 + trackerService.addTrackedResources(newIntent.id(), newInstallable.resources());
790 + try {
791 + batches.addAll(getInstaller(newInstallable).replace(oldInstallable, newInstallable));
792 + } catch (IntentException e) {
793 + log.warn("Unable to update intent {} due to:", oldIntent.id(), e);
794 + //FIXME... we failed. need to uninstall (if same) or revert (if different)
795 + trackerService.removeTrackedResources(newIntent.id(), newInstallable.resources());
796 + exception = e;
797 + batches = uninstallIntent(oldIntent, oldInstallables);
798 + }
799 + }
800 + return batches;
801 + }
802 + }
803 +
804 + private class Installed extends CompletedIntentUpdate {
805 +
806 + private final Intent intent;
807 + private final List<Intent> installables;
808 + private IntentState intentState;
809 + private final List<FlowRuleBatchOperation> batches;
810 + private int currentBatch = 0;
811 +
812 + Installed(Intent intent, List<Intent> installables, List<FlowRuleBatchOperation> batches) {
813 + this.intent = checkNotNull(intent);
814 + this.installables = ImmutableList.copyOf(checkNotNull(installables));
815 + this.batches = new LinkedList<>(checkNotNull(batches));
816 + this.intentState = INSTALLING;
817 + }
818 +
819 + @Override
820 + public void batchSuccess() {
821 + currentBatch++;
822 + }
823 +
824 + @Override
825 + public List<Intent> allInstallables() {
826 + return installables;
827 + }
828 +
829 + @Override
830 + public void writeAfterExecution(BatchWrite batchWrite) {
831 + switch (intentState) {
832 + case INSTALLING:
833 + batchWrite.setState(intent, INSTALLED);
834 + batchWrite.setInstallableIntents(intent.id(), this.installables);
607 break; 835 break;
608 - case UPDATE: 836 + case FAILED:
609 - oldIntent = store.getIntent(op.intentId()); 837 + batchWrite.setState(intent, FAILED);
610 - newIntent = oldIntent; 838 + batchWrite.removeInstalledIntents(intent.id());
611 break; 839 break;
612 default: 840 default:
613 - oldIntent = null;
614 - newIntent = null;
615 break; 841 break;
616 } 842 }
617 - // fetch the old intent's installables from the store
618 - if (oldIntent != null) {
619 - oldInstallables = store.getInstallableIntents(oldIntent.id());
620 - } else {
621 - oldInstallables = null;
622 - if (newIntent == null) {
623 - log.info("Ignoring {} for missing Intent {}", op.type(), op.intentId());
624 - }
625 - }
626 } 843 }
627 844
628 - void init(BatchWrite batchWrite) { 845 + @Override
629 - if (newIntent != null) { 846 + public FlowRuleBatchOperation currentBatch() {
630 - // TODO consider only "creating" intent if it does not exist 847 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
631 - // Note: We need to set state to INSTALL_REQ regardless. 848 + }
632 - batchWrite.createIntent(newIntent); 849 +
633 - } else if (oldIntent != null && !oldIntent.equals(newIntent)) { 850 + @Override
634 - batchWrite.setState(oldIntent, WITHDRAW_REQ); 851 + public void batchFailed() {
852 + // the current batch has failed, so recompile
853 + // remove the current batch and all remaining
854 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
855 + batches.remove(i);
635 } 856 }
857 + intentState = FAILED;
858 + batches.addAll(uninstallIntent(intent, installables));
859 +
860 + // TODO we might want to try to recompile the new intent
636 } 861 }
862 + }
637 863
638 - Intent oldIntent() { 864 + private class Withdrawn extends CompletedIntentUpdate {
639 - return oldIntent; 865 +
866 + private final Intent intent;
867 + private final List<Intent> installables;
868 + private final List<FlowRuleBatchOperation> batches;
869 + private int currentBatch;
870 +
871 + Withdrawn(Intent intent, List<Intent> installables, List<FlowRuleBatchOperation> batches) {
872 + this.intent = checkNotNull(intent);
873 + this.installables = ImmutableList.copyOf(installables);
874 + this.batches = new LinkedList<>(batches);
875 + this.currentBatch = 0;
640 } 876 }
641 877
642 - Intent newIntent() { 878 + @Override
643 - return newIntent; 879 + public List<Intent> allInstallables() {
880 + return installables;
644 } 881 }
645 882
646 - List<Intent> oldInstallables() { 883 + @Override
647 - return oldInstallables; 884 + public void batchSuccess() {
885 + currentBatch++;
648 } 886 }
649 887
650 - List<Intent> newInstallables() { 888 + @Override
651 - return newInstallables; 889 + public void writeAfterExecution(BatchWrite batchWrite) {
890 + batchWrite.setState(intent, WITHDRAWN);
891 + batchWrite.removeInstalledIntents(intent.id());
892 + batchWrite.removeIntent(intent.id());
652 } 893 }
653 894
654 - void setInstallables(List<Intent> installables) { 895 + @Override
655 - newInstallables = installables; 896 + public FlowRuleBatchOperation currentBatch() {
897 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
656 } 898 }
657 899
658 - boolean isComplete() { 900 + @Override
659 - return currentBatch >= batches.size(); 901 + public void batchFailed() {
902 + // the current batch has failed, so recompile
903 + // remove the current batch and all remaining
904 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
905 + batches.remove(i);
906 + }
907 + batches.addAll(uninstallIntent(intent, installables));
660 } 908 }
909 + }
910 +
911 + private class Replaced extends CompletedIntentUpdate {
912 +
913 + private final Intent newIntent;
914 + private final Intent oldIntent;
915 +
916 + private final List<Intent> newInstallables;
917 + private final List<Intent> oldInstallables;
918 + private final List<FlowRuleBatchOperation> batches;
919 + private int currentBatch;
920 +
921 + Replaced(Intent newIntent, Intent oldIntent,
922 + List<Intent> newInstallables, List<Intent> oldInstallables,
923 + List<FlowRuleBatchOperation> batches) {
924 + this.newIntent = checkNotNull(newIntent);
925 + this.oldIntent = checkNotNull(oldIntent);
926 + this.newInstallables = ImmutableList.copyOf(checkNotNull(newInstallables));
927 + this.oldInstallables = ImmutableList.copyOf(checkNotNull(oldInstallables));
928 + this.batches = new LinkedList<>(batches);
929 + this.currentBatch = 0;
930 + }
931 +
932 + @Override
933 + public List<Intent> allInstallables() {
934 + LinkedList<Intent> allInstallables = new LinkedList<>();
935 + allInstallables.addAll(newInstallables);
936 + allInstallables.addAll(oldInstallables);
661 937
662 - FlowRuleBatchOperation currentBatch() { 938 + return allInstallables;
663 - return !isComplete() ? batches.get(currentBatch) : null;
664 } 939 }
665 940
666 - void batchSuccess() { 941 + @Override
667 - // move on to next Batch 942 + public void batchSuccess() {
668 currentBatch++; 943 currentBatch++;
669 } 944 }
670 945
671 - void batchFailed() { 946 + @Override
947 + public void writeAfterExecution(BatchWrite batchWrite) {
948 + batchWrite.setState(newIntent, INSTALLED);
949 + batchWrite.setInstallableIntents(newIntent.id(), newInstallables);
950 +
951 + batchWrite.setState(oldIntent, WITHDRAWN);
952 + batchWrite.removeInstalledIntents(oldIntent.id());
953 + batchWrite.removeIntent(oldIntent.id());
954 + }
955 +
956 + @Override
957 + public FlowRuleBatchOperation currentBatch() {
958 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
959 + }
960 +
961 + @Override
962 + public void batchFailed() {
672 // the current batch has failed, so recompile 963 // the current batch has failed, so recompile
673 // remove the current batch and all remaining 964 // remove the current batch and all remaining
674 - for (int i = currentBatch; i < batches.size(); i++) { 965 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
675 batches.remove(i); 966 batches.remove(i);
676 } 967 }
677 - if (oldIntent != null) { 968 + batches.addAll(uninstallIntent(oldIntent, oldInstallables));
678 - executeWithdrawingPhase(this); // remove the old intent 969 +
970 + batches.addAll(uninstallIntent(newIntent, newInstallables));
971 +
972 + // TODO we might want to try to recompile the new intent
973 + }
974 + }
975 +
976 + private class CompilingFailed extends CompletedIntentUpdate {
977 +
978 + private final Intent intent;
979 + private final IntentException exception;
980 +
981 + CompilingFailed(Intent intent, IntentException exception) {
982 + this.intent = checkNotNull(intent);
983 + this.exception = checkNotNull(exception);
984 + }
985 +
986 + @Override
987 + public Optional<IntentUpdate> execute() {
988 + if (exception instanceof PathNotFoundException) {
989 + log.debug("Path not found for intent {}", intent);
990 + } else {
991 + log.warn("Unable to compile intent {} due to:", intent.id(), exception);
679 } 992 }
680 - if (newIntent != null) { 993 +
681 - setInflightState(newIntent, FAILED); 994 + return Optional.empty();
682 - batches.addAll(uninstallIntent(newIntent, newInstallables())); 995 + }
996 +
997 + @Override
998 + public void writeAfterExecution(BatchWrite batchWrite) {
999 + batchWrite.setState(intent, FAILED);
1000 + batchWrite.removeInstalledIntents(intent.id());
1001 + }
1002 + }
1003 +
1004 + private class InstallingFailed extends CompletedIntentUpdate {
1005 +
1006 + private final Intent intent;
1007 + private final List<Intent> installables;
1008 + private final List<FlowRuleBatchOperation> batches;
1009 + private int currentBatch = 0;
1010 +
1011 + InstallingFailed(Intent intent, List<Intent> installables, List<FlowRuleBatchOperation> batches) {
1012 + this.intent = checkNotNull(intent);
1013 + this.installables = ImmutableList.copyOf(checkNotNull(installables));
1014 + this.batches = new LinkedList<>(checkNotNull(batches));
1015 + }
1016 +
1017 + @Override
1018 + public List<Intent> allInstallables() {
1019 + return installables;
1020 + }
1021 +
1022 + @Override
1023 + public void batchSuccess() {
1024 + currentBatch++;
1025 + }
1026 +
1027 + @Override
1028 + public void writeAfterExecution(BatchWrite batchWrite) {
1029 + batchWrite.setState(intent, FAILED);
1030 + batchWrite.removeInstalledIntents(intent.id());
1031 + }
1032 +
1033 + @Override
1034 + public FlowRuleBatchOperation currentBatch() {
1035 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
1036 + }
1037 +
1038 + @Override
1039 + public void batchFailed() {
1040 + // the current batch has failed, so recompile
1041 + // remove the current batch and all remaining
1042 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
1043 + batches.remove(i);
683 } 1044 }
1045 + batches.addAll(uninstallIntent(intent, installables));
684 1046
685 // TODO we might want to try to recompile the new intent 1047 // TODO we might want to try to recompile the new intent
686 } 1048 }
1049 + }
687 1050
688 - private void finalizeStates(BatchWrite batchWrite) { 1051 + private class ReplacingFailed extends CompletedIntentUpdate {
689 - // events to be triggered on successful write
690 - for (Intent intent : stateMap.keySet()) {
691 - switch (getInflightState(intent)) {
692 - case INSTALLING:
693 - batchWrite.setState(intent, INSTALLED);
694 - batchWrite.setInstallableIntents(newIntent.id(), newInstallables);
695 - break;
696 - case WITHDRAWING:
697 - batchWrite.setState(intent, WITHDRAWN);
698 - batchWrite.removeInstalledIntents(intent.id());
699 - batchWrite.removeIntent(intent.id());
700 - break;
701 - case FAILED:
702 - batchWrite.setState(intent, FAILED);
703 - batchWrite.removeInstalledIntents(intent.id());
704 - break;
705 1052
706 - // FALLTHROUGH to default from here 1053 + private final Intent newIntent;
707 - case INSTALL_REQ: 1054 + private final Intent oldIntent;
708 - case COMPILING: 1055 + private final List<Intent> newInstallables;
709 - case RECOMPILING: 1056 + private final List<Intent> oldInstallables;
710 - case WITHDRAW_REQ: 1057 + private final List<FlowRuleBatchOperation> batches;
711 - case WITHDRAWN: 1058 + private int currentBatch;
712 - case INSTALLED: 1059 +
713 - default: 1060 + ReplacingFailed(Intent newIntent, Intent oldIntent,
714 - //FIXME clean this up (we shouldn't ever get here) 1061 + List<Intent> newInstallables, List<Intent> oldInstallables,
715 - log.warn("Bad state: {} for {}", getInflightState(intent), intent); 1062 + List<FlowRuleBatchOperation> batches) {
716 - break; 1063 + this.newIntent = checkNotNull(newIntent);
717 - } 1064 + this.oldIntent = checkNotNull(oldIntent);
718 - } 1065 + this.newInstallables = ImmutableList.copyOf(checkNotNull(newInstallables));
1066 + this.oldInstallables = ImmutableList.copyOf(checkNotNull(oldInstallables));
1067 + this.batches = new LinkedList<>(batches);
1068 + this.currentBatch = 0;
1069 + }
1070 +
1071 + @Override
1072 + public List<Intent> allInstallables() {
1073 + LinkedList<Intent> allInstallables = new LinkedList<>();
1074 + allInstallables.addAll(newInstallables);
1075 + allInstallables.addAll(oldInstallables);
1076 +
1077 + return allInstallables;
719 } 1078 }
720 1079
721 - void addBatches(List<FlowRuleBatchOperation> batches) { 1080 + @Override
722 - this.batches.addAll(batches); 1081 + public void batchSuccess() {
1082 + currentBatch++;
723 } 1083 }
724 1084
725 - IntentState getInflightState(Intent intent) { 1085 + @Override
726 - return stateMap.get(intent); 1086 + public void writeAfterExecution(BatchWrite batchWrite) {
1087 + batchWrite.setState(newIntent, FAILED);
1088 + batchWrite.removeInstalledIntents(newIntent.id());
1089 +
1090 + batchWrite.setState(oldIntent, WITHDRAWN);
1091 + batchWrite.removeInstalledIntents(oldIntent.id());
1092 + batchWrite.removeIntent(oldIntent.id());
727 } 1093 }
728 1094
729 - // set transient state during intent update process 1095 + @Override
730 - void setInflightState(Intent intent, IntentState newState) { 1096 + public FlowRuleBatchOperation currentBatch() {
731 - // This method should be called for 1097 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
732 - // transition to non-parking or Failed only 1098 + }
733 - if (!NON_PARKED_OR_FAILED.contains(newState)) { 1099 +
734 - log.error("Unexpected transition to {}", newState); 1100 + @Override
1101 + public void batchFailed() {
1102 + // the current batch has failed, so recompile
1103 + // remove the current batch and all remaining
1104 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
1105 + batches.remove(i);
735 } 1106 }
1107 + batches.addAll(uninstallIntent(oldIntent, oldInstallables));
736 1108
737 - IntentState oldState = stateMap.get(intent); 1109 + batches.addAll(uninstallIntent(newIntent, newInstallables));
738 - log.debug("intent id: {}, old state: {}, new state: {}",
739 - intent.id(), oldState, newState);
740 1110
741 - stateMap.put(intent, newState); 1111 + // TODO we might want to try to recompile the new intent
742 } 1112 }
743 } 1113 }
744 1114
745 - private class IntentInstallMonitor implements Runnable { 1115 + private class IntentBatchPreprocess implements Runnable {
746 1116
747 - // TODO make this configurable through a configuration file using @Property mechanism 1117 + // TODO make this configurable
748 - // These fields needs to be moved to the enclosing class and configurable through a configuration file
749 private static final int TIMEOUT_PER_OP = 500; // ms 1118 private static final int TIMEOUT_PER_OP = 500; // ms
750 - private static final int MAX_ATTEMPTS = 3; 1119 + protected static final int MAX_ATTEMPTS = 3;
751 1120
752 - private final IntentOperations ops; 1121 + protected final IntentOperations ops;
753 - private final List<IntentUpdate> intentUpdates = Lists.newArrayList();
754 -
755 - private final Duration timeoutPerOperation;
756 - private final int maxAttempts;
757 1122
758 // future holding current FlowRuleBatch installation result 1123 // future holding current FlowRuleBatch installation result
759 - private Future<CompletedBatchOperation> future; 1124 + protected final long startTime = System.currentTimeMillis();
760 - private long startTime = System.currentTimeMillis(); 1125 + protected final long endTime;
761 - private long endTime;
762 - private int installAttempt;
763 1126
764 - public IntentInstallMonitor(IntentOperations ops) { 1127 + private IntentBatchPreprocess(IntentOperations ops, long endTime) {
765 - this(ops, Duration.ofMillis(TIMEOUT_PER_OP), MAX_ATTEMPTS); 1128 + this.ops = checkNotNull(ops);
1129 + this.endTime = endTime;
766 } 1130 }
767 1131
768 - public IntentInstallMonitor(IntentOperations ops, Duration timeoutPerOperation, int maxAttempts) { 1132 + public IntentBatchPreprocess(IntentOperations ops) {
769 - this.ops = checkNotNull(ops); 1133 + this(ops, System.currentTimeMillis() + ops.operations().size() * TIMEOUT_PER_OP);
770 - this.timeoutPerOperation = checkNotNull(timeoutPerOperation); 1134 + }
771 - checkArgument(maxAttempts > 0, "maxAttempts must be larger than 0, but %s", maxAttempts);
772 - this.maxAttempts = maxAttempts;
773 1135
774 - resetTimeoutLimit(); 1136 + // FIXME compute reasonable timeouts
1137 + protected long calculateTimeoutLimit() {
1138 + return System.currentTimeMillis() + ops.operations().size() * TIMEOUT_PER_OP;
775 } 1139 }
776 1140
777 - private void resetTimeoutLimit() { 1141 + @Override
778 - // FIXME compute reasonable timeouts 1142 + public void run() {
779 - this.endTime = System.currentTimeMillis() 1143 + try {
780 - + ops.operations().size() * timeoutPerOperation.toMillis(); 1144 + // this should only be called on the first iteration
1145 + // note: this a "expensive", so it is not done in the constructor
1146 +
1147 + // - creates per Intent installation context (IntentUpdate)
1148 + // - write Intents to store
1149 + // - process (compile, install, etc.) each Intents
1150 + // - generate FlowRuleBatch for this phase
1151 + // build IntentUpdates
1152 + List<IntentUpdate> updates = createIntentUpdates();
1153 +
1154 + // Write batch information
1155 + BatchWrite batchWrite = createBatchWrite(updates);
1156 + writeBatch(batchWrite);
1157 +
1158 + new IntentBatchApplyFirst(ops, processIntentUpdates(updates), endTime, 0, null).run();
1159 + } catch (Exception e) {
1160 + log.error("Error submitting batches:", e);
1161 + // FIXME incomplete Intents should be cleaned up
1162 + // (transition to FAILED, etc.)
1163 +
1164 + // TODO: remove duplicate due to inlining
1165 + // the batch has failed
1166 + // TODO: maybe we should do more?
1167 + log.error("Walk the plank, matey...");
1168 + batchService.removeIntentOperations(ops);
1169 + }
1170 + }
1171 +
1172 + private List<IntentUpdate> createIntentUpdates() {
1173 + return ops.operations().stream()
1174 + .map(IntentManager.this::createIntentUpdate)
1175 + .collect(Collectors.toList());
781 } 1176 }
782 1177
783 - private void buildIntentUpdates() { 1178 + private BatchWrite createBatchWrite(List<IntentUpdate> updates) {
784 BatchWrite batchWrite = BatchWrite.newInstance(); 1179 BatchWrite batchWrite = BatchWrite.newInstance();
1180 + updates.forEach(update -> update.writeBeforeExecution(batchWrite));
1181 + return batchWrite;
1182 + }
785 1183
786 - // create context and record new request to store 1184 + private List<CompletedIntentUpdate> processIntentUpdates(List<IntentUpdate> updates) {
787 - for (IntentOperation op : ops.operations()) { 1185 + // start processing each Intents
788 - IntentUpdate update = new IntentUpdate(op); 1186 + List<CompletedIntentUpdate> completed = new ArrayList<>();
789 - update.init(batchWrite); 1187 + for (IntentUpdate update : updates) {
790 - intentUpdates.add(update); 1188 + Optional<IntentUpdate> phase = Optional.of(update);
1189 + IntentUpdate previous = update;
1190 + while (true) {
1191 + if (!phase.isPresent()) {
1192 + // FIXME: not type safe cast
1193 + completed.add((CompletedIntentUpdate) previous);
1194 + break;
1195 + }
1196 + previous = phase.get();
1197 + phase = previous.execute();
1198 + }
791 } 1199 }
792 1200
1201 + return completed;
1202 + }
1203 +
1204 + protected void writeBatch(BatchWrite batchWrite) {
793 if (!batchWrite.isEmpty()) { 1205 if (!batchWrite.isEmpty()) {
794 store.batchWrite(batchWrite); 1206 store.batchWrite(batchWrite);
795 } 1207 }
1208 + }
1209 + }
796 1210
797 - // start processing each Intents 1211 + // TODO: better naming
798 - for (IntentUpdate update : intentUpdates) { 1212 + private class IntentBatchApplyFirst extends IntentBatchPreprocess {
799 - processIntentUpdate(update); 1213 +
800 - } 1214 + protected final List<CompletedIntentUpdate> intentUpdates;
801 - future = applyNextBatch(); 1215 + protected final int installAttempt;
1216 + protected Future<CompletedBatchOperation> future;
1217 +
1218 + IntentBatchApplyFirst(IntentOperations operations, List<CompletedIntentUpdate> intentUpdates,
1219 + long endTime, int installAttempt, Future<CompletedBatchOperation> future) {
1220 + super(operations, endTime);
1221 + this.intentUpdates = ImmutableList.copyOf(intentUpdates);
1222 + this.future = future;
1223 + this.installAttempt = installAttempt;
1224 + }
1225 +
1226 + @Override
1227 + public void run() {
1228 + Future<CompletedBatchOperation> future = applyNextBatch(intentUpdates);
1229 + new IntentBatchProcessFutures(ops, intentUpdates, endTime, installAttempt, future).run();
802 } 1230 }
803 1231
804 /** 1232 /**
...@@ -806,84 +1234,127 @@ public class IntentManager ...@@ -806,84 +1234,127 @@ public class IntentManager
806 * 1234 *
807 * @return Future for next batch 1235 * @return Future for next batch
808 */ 1236 */
809 - private Future<CompletedBatchOperation> applyNextBatch() { 1237 + protected Future<CompletedBatchOperation> applyNextBatch(List<CompletedIntentUpdate> updates) {
810 //TODO test this. (also, maybe save this batch) 1238 //TODO test this. (also, maybe save this batch)
811 - FlowRuleBatchOperation batch = new FlowRuleBatchOperation(Collections.emptyList()); 1239 + FlowRuleBatchOperation batch = createFlowRuleBatchOperation(updates);
812 - for (IntentUpdate update : intentUpdates) {
813 - if (!update.isComplete()) {
814 - batch.addAll(update.currentBatch());
815 - }
816 - }
817 if (batch.size() > 0) { 1240 if (batch.size() > 0) {
818 //FIXME apply batch might throw an exception 1241 //FIXME apply batch might throw an exception
819 return flowRuleService.applyBatch(batch); 1242 return flowRuleService.applyBatch(batch);
820 } else { 1243 } else {
821 // there are no flow rule batches; finalize the intent update 1244 // there are no flow rule batches; finalize the intent update
822 - BatchWrite batchWrite = BatchWrite.newInstance(); 1245 + BatchWrite batchWrite = createFinalizedBatchWrite(updates);
823 - for (IntentUpdate update : intentUpdates) { 1246 +
824 - update.finalizeStates(batchWrite); 1247 + writeBatch(batchWrite);
825 - }
826 - if (!batchWrite.isEmpty()) {
827 - store.batchWrite(batchWrite);
828 - }
829 return null; 1248 return null;
830 } 1249 }
831 } 1250 }
832 1251
833 - private void updateBatches(CompletedBatchOperation completed) { 1252 + private FlowRuleBatchOperation createFlowRuleBatchOperation(List<CompletedIntentUpdate> intentUpdates) {
834 - if (completed.isSuccess()) { 1253 + FlowRuleBatchOperation batch = new FlowRuleBatchOperation(Collections.emptyList());
835 - for (IntentUpdate update : intentUpdates) { 1254 + for (CompletedIntentUpdate update : intentUpdates) {
836 - update.batchSuccess(); 1255 + FlowRuleBatchOperation currentBatch = update.currentBatch();
1256 + if (currentBatch != null) {
1257 + batch.addAll(currentBatch);
837 } 1258 }
838 - } else { 1259 + }
839 - // entire batch has been reverted... 1260 + return batch;
840 - log.debug("Failed items: {}", completed.failedItems()); 1261 + }
841 - log.debug("Failed ids: {}", completed.failedIds());
842 1262
843 - for (Long id : completed.failedIds()) { 1263 + private BatchWrite createFinalizedBatchWrite(List<CompletedIntentUpdate> intentUpdates) {
844 - IntentId targetId = IntentId.valueOf(id); 1264 + BatchWrite batchWrite = BatchWrite.newInstance();
845 - for (IntentUpdate update : intentUpdates) { 1265 + for (CompletedIntentUpdate update : intentUpdates) {
846 - List<Intent> installables = Lists.newArrayList(update.newInstallables()); 1266 + update.writeAfterExecution(batchWrite);
847 - if (update.oldInstallables() != null) {
848 - installables.addAll(update.oldInstallables());
849 - }
850 - for (Intent intent : installables) {
851 - if (intent.id().equals(targetId)) {
852 - update.batchFailed();
853 - break;
854 - }
855 - }
856 - }
857 - // don't increment the non-failed items, as they have been reverted.
858 - }
859 } 1267 }
1268 + return batchWrite;
860 } 1269 }
861 1270
862 - private void abandonShip() { 1271 + protected void abandonShip() {
863 // the batch has failed 1272 // the batch has failed
864 // TODO: maybe we should do more? 1273 // TODO: maybe we should do more?
865 log.error("Walk the plank, matey..."); 1274 log.error("Walk the plank, matey...");
866 future = null; 1275 future = null;
867 batchService.removeIntentOperations(ops); 1276 batchService.removeIntentOperations(ops);
868 } 1277 }
1278 + }
1279 +
1280 + // TODO: better naming
1281 + private class IntentBatchProcessFutures extends IntentBatchApplyFirst {
1282 +
1283 + IntentBatchProcessFutures(IntentOperations operations, List<CompletedIntentUpdate> intentUpdates,
1284 + long endTime, int installAttempt, Future<CompletedBatchOperation> future) {
1285 + super(operations, intentUpdates, endTime, installAttempt, future);
1286 + }
1287 +
1288 + @Override
1289 + public void run() {
1290 + try {
1291 + // - peek if current FlowRuleBatch is complete
1292 + // -- If complete OK:
1293 + // step each IntentUpdate forward
1294 + // If phase left: generate next FlowRuleBatch
1295 + // If no more phase: write parking states
1296 + // -- If complete FAIL:
1297 + // Intent which failed: transition Intent to FAILED
1298 + // Other Intents: resubmit same FlowRuleBatch for this phase
1299 + Future<CompletedBatchOperation> future = processFutures();
1300 + if (future == null) {
1301 + // there are no outstanding batches; we are done
1302 + batchService.removeIntentOperations(ops);
1303 + } else if (System.currentTimeMillis() > endTime) {
1304 + // - cancel current FlowRuleBatch and resubmit again
1305 + retry();
1306 + } else {
1307 + // we are not done yet, yield the thread by resubmitting ourselves
1308 + executor.submit(new IntentBatchProcessFutures(ops, intentUpdates, endTime, installAttempt, future));
1309 + }
1310 + } catch (Exception e) {
1311 + log.error("Error submitting batches:", e);
1312 + // FIXME incomplete Intents should be cleaned up
1313 + // (transition to FAILED, etc.)
1314 + abandonShip();
1315 + }
1316 + }
869 1317
870 /** 1318 /**
871 * Iterate through the pending futures, and remove them when they have completed. 1319 * Iterate through the pending futures, and remove them when they have completed.
872 */ 1320 */
873 - private void processFutures() { 1321 + private Future<CompletedBatchOperation> processFutures() {
874 - if (future == null) {
875 - // we are done if the future is null
876 - return;
877 - }
878 try { 1322 try {
879 CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS); 1323 CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS);
880 updateBatches(completed); 1324 updateBatches(completed);
881 - future = applyNextBatch(); 1325 + return applyNextBatch(intentUpdates);
882 } catch (TimeoutException | InterruptedException te) { 1326 } catch (TimeoutException | InterruptedException te) {
883 log.trace("Installation of intents are still pending: {}", ops); 1327 log.trace("Installation of intents are still pending: {}", ops);
1328 + return future;
884 } catch (ExecutionException e) { 1329 } catch (ExecutionException e) {
885 log.warn("Execution of batch failed: {}", ops, e); 1330 log.warn("Execution of batch failed: {}", ops, e);
886 abandonShip(); 1331 abandonShip();
1332 + return future;
1333 + }
1334 + }
1335 +
1336 + private void updateBatches(CompletedBatchOperation completed) {
1337 + if (completed.isSuccess()) {
1338 + for (CompletedIntentUpdate update : intentUpdates) {
1339 + update.batchSuccess();
1340 + }
1341 + } else {
1342 + // entire batch has been reverted...
1343 + log.debug("Failed items: {}", completed.failedItems());
1344 + log.debug("Failed ids: {}", completed.failedIds());
1345 +
1346 + for (Long id : completed.failedIds()) {
1347 + IntentId targetId = IntentId.valueOf(id);
1348 + for (CompletedIntentUpdate update : intentUpdates) {
1349 + for (Intent intent : update.allInstallables()) {
1350 + if (intent.id().equals(targetId)) {
1351 + update.batchFailed();
1352 + break;
1353 + }
1354 + }
1355 + }
1356 + // don't increment the non-failed items, as they have been reverted.
1357 + }
887 } 1358 }
888 } 1359 }
889 1360
...@@ -891,19 +1362,19 @@ public class IntentManager ...@@ -891,19 +1362,19 @@ public class IntentManager
891 log.debug("Execution timed out, retrying."); 1362 log.debug("Execution timed out, retrying.");
892 if (future.cancel(true)) { // cancel success; batch is reverted 1363 if (future.cancel(true)) { // cancel success; batch is reverted
893 // reset the timer 1364 // reset the timer
894 - resetTimeoutLimit(); 1365 + long timeLimit = calculateTimeoutLimit();
895 - installAttempt++; 1366 + int attempts = installAttempt + 1;
896 - if (installAttempt == maxAttempts) { 1367 + if (attempts == MAX_ATTEMPTS) {
897 log.warn("Install request timed out: {}", ops); 1368 log.warn("Install request timed out: {}", ops);
898 - for (IntentUpdate update : intentUpdates) { 1369 + for (CompletedIntentUpdate update : intentUpdates) {
899 update.batchFailed(); 1370 update.batchFailed();
900 } 1371 }
901 - } else if (installAttempt > maxAttempts) { 1372 + } else if (attempts > MAX_ATTEMPTS) {
902 abandonShip(); 1373 abandonShip();
903 return; 1374 return;
904 } // else just resubmit the work 1375 } // else just resubmit the work
905 - future = applyNextBatch(); 1376 + Future<CompletedBatchOperation> future = applyNextBatch(intentUpdates);
906 - executor.submit(this); 1377 + executor.submit(new IntentBatchProcessFutures(ops, intentUpdates, timeLimit, attempts, future));
907 } else { 1378 } else {
908 log.error("Cancelling FlowRuleBatch failed."); 1379 log.error("Cancelling FlowRuleBatch failed.");
909 // FIXME 1380 // FIXME
...@@ -913,51 +1384,6 @@ public class IntentManager ...@@ -913,51 +1384,6 @@ public class IntentManager
913 abandonShip(); 1384 abandonShip();
914 } 1385 }
915 } 1386 }
916 -
917 - boolean isComplete() {
918 - return future == null;
919 - }
920 -
921 - @Override
922 - public void run() {
923 - try {
924 - if (intentUpdates.isEmpty()) {
925 - // this should only be called on the first iteration
926 - // note: this a "expensive", so it is not done in the constructor
927 -
928 - // - creates per Intent installation context (IntentUpdate)
929 - // - write Intents to store
930 - // - process (compile, install, etc.) each Intents
931 - // - generate FlowRuleBatch for this phase
932 - buildIntentUpdates();
933 - }
934 -
935 - // - peek if current FlowRuleBatch is complete
936 - // -- If complete OK:
937 - // step each IntentUpdate forward
938 - // If phase left: generate next FlowRuleBatch
939 - // If no more phase: write parking states
940 - // -- If complete FAIL:
941 - // Intent which failed: transition Intent to FAILED
942 - // Other Intents: resubmit same FlowRuleBatch for this phase
943 - processFutures();
944 - if (isComplete()) {
945 - // there are no outstanding batches; we are done
946 - batchService.removeIntentOperations(ops);
947 - } else if (endTime < System.currentTimeMillis()) {
948 - // - cancel current FlowRuleBatch and resubmit again
949 - retry();
950 - } else {
951 - // we are not done yet, yield the thread by resubmitting ourselves
952 - executor.submit(this);
953 - }
954 - } catch (Exception e) {
955 - log.error("Error submitting batches:", e);
956 - // FIXME incomplete Intents should be cleaned up
957 - // (transition to FAILED, etc.)
958 - abandonShip();
959 - }
960 - }
961 } 1387 }
962 1388
963 private class InternalBatchDelegate implements IntentBatchDelegate { 1389 private class InternalBatchDelegate implements IntentBatchDelegate {
...@@ -966,7 +1392,7 @@ public class IntentManager ...@@ -966,7 +1392,7 @@ public class IntentManager
966 log.info("Execute {} operation(s).", operations.operations().size()); 1392 log.info("Execute {} operation(s).", operations.operations().size());
967 log.debug("Execute operations: {}", operations.operations()); 1393 log.debug("Execute operations: {}", operations.operations());
968 //FIXME: perhaps we want to track this task so that we can cancel it. 1394 //FIXME: perhaps we want to track this task so that we can cancel it.
969 - executor.execute(new IntentInstallMonitor(operations)); 1395 + executor.execute(new IntentBatchPreprocess(operations));
970 } 1396 }
971 1397
972 @Override 1398 @Override
...@@ -975,5 +1401,4 @@ public class IntentManager ...@@ -975,5 +1401,4 @@ public class IntentManager
975 log.warn("NOT IMPLEMENTED -- Cancel operations: {}", operations); 1401 log.warn("NOT IMPLEMENTED -- Cancel operations: {}", operations);
976 } 1402 }
977 } 1403 }
978 -
979 } 1404 }
......