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;
...@@ -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,9 +312,6 @@ public class IntentManager ...@@ -384,9 +312,6 @@ 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(),
...@@ -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,378 +436,711 @@ public class IntentManager ...@@ -550,378 +436,711 @@ 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);
563 } 448 }
564 - 449 + List<Intent> installables = store.getInstallableIntents(oldIntent.id());
565 - if (update.oldInstallables() != null && update.newInstallables() != null) { 450 + if (installables == null) {
566 - executeReplacementPhase(update); 451 + return new WithdrawStateChange1(oldIntent);
567 - } else if (update.newInstallables() != null) { 452 + }
568 - executeInstallingPhase(update); 453 + return new WithdrawRequest(oldIntent, installables);
569 - } else if (update.oldInstallables() != null) { 454 + }
570 - executeWithdrawingPhase(update); 455 + case REPLACE: {
456 + Intent newIntent = operation.intent();
457 + Intent oldIntent = store.getIntent(operation.intentId());
458 + if (oldIntent == null) {
459 + return new InstallRequest(newIntent);
460 + }
461 + List<Intent> installables = store.getInstallableIntents(oldIntent.id());
462 + if (installables == null) {
463 + if (newIntent.equals(oldIntent)) {
464 + return new InstallRequest(newIntent);
571 } else { 465 } else {
572 - if (update.oldIntent() != null && 466 + return new WithdrawStateChange2(oldIntent);
573 - !update.oldIntent().equals(update.newIntent())) {
574 - // removing failed intent
575 - update.setInflightState(update.oldIntent(), WITHDRAWING);
576 } 467 }
577 -// if (update.newIntent() != null) { 468 + }
578 -// // TODO assert that next state is failed 469 + return new ReplaceRequest(newIntent, oldIntent, installables);
579 -// } 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 {
585 - private final Intent oldIntent;
586 - private final Intent newIntent;
587 - private final Map<Intent, IntentState> stateMap = Maps.newHashMap();
588 489
589 - private final List<Intent> oldInstallables; 490 + /**
590 - private List<Intent> newInstallables; 491 + * Execute the procedure represented by the instance
591 - private final List<FlowRuleBatchOperation> batches = Lists.newLinkedList(); 492 + * and generates the next update instance.
592 - private int currentBatch = 0; // TODO: maybe replace with an iterator 493 + *
494 + * @return next update
495 + */
496 + public Optional<IntentUpdate> execute() {
497 + return Optional.empty();
498 + }
593 499
594 - IntentUpdate(IntentOperation op) { 500 + /**
595 - switch (op.type()) { 501 + * Write data to the specified BatchWrite before execution() is called.
596 - case SUBMIT: 502 + *
597 - newIntent = op.intent(); 503 + * @param batchWrite batchWrite
598 - oldIntent = null; 504 + */
599 - break; 505 + public void writeBeforeExecution(BatchWrite batchWrite) {}
600 - case WITHDRAW:
601 - newIntent = null;
602 - oldIntent = store.getIntent(op.intentId());
603 - break;
604 - case REPLACE:
605 - newIntent = op.intent();
606 - oldIntent = store.getIntent(op.intentId());
607 - break;
608 - case UPDATE:
609 - oldIntent = store.getIntent(op.intentId());
610 - newIntent = oldIntent;
611 - break;
612 - default:
613 - oldIntent = null;
614 - newIntent = null;
615 - break;
616 } 506 }
617 - // fetch the old intent's installables from the store 507 +
618 - if (oldIntent != null) { 508 + private abstract class CompletedIntentUpdate extends IntentUpdate {
619 - oldInstallables = store.getInstallableIntents(oldIntent.id()); 509 +
620 - } else { 510 + /**
621 - oldInstallables = null; 511 + * Write data to the specified BatchWrite after execution() is called.
622 - if (newIntent == null) { 512 + *
623 - log.info("Ignoring {} for missing Intent {}", op.type(), op.intentId()); 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();
624 } 537 }
625 } 538 }
539 +
540 + private class InstallRequest extends IntentUpdate {
541 +
542 + private final Intent intent;
543 +
544 + InstallRequest(Intent intent) {
545 + this.intent = checkNotNull(intent);
626 } 546 }
627 547
628 - void init(BatchWrite batchWrite) { 548 + @Override
629 - if (newIntent != null) { 549 + public void writeBeforeExecution(BatchWrite batchWrite) {
630 // TODO consider only "creating" intent if it does not exist 550 // TODO consider only "creating" intent if it does not exist
631 // Note: We need to set state to INSTALL_REQ regardless. 551 // Note: We need to set state to INSTALL_REQ regardless.
632 - batchWrite.createIntent(newIntent); 552 + batchWrite.createIntent(intent);
633 - } else if (oldIntent != null && !oldIntent.equals(newIntent)) { 553 + }
634 - batchWrite.setState(oldIntent, WITHDRAW_REQ); 554 +
555 + @Override
556 + public Optional<IntentUpdate> execute() {
557 + return Optional.of(new Compiling(intent));
558 + }
635 } 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));
636 } 569 }
637 570
638 - Intent oldIntent() { 571 + @Override
639 - return oldIntent; 572 + public void writeBeforeExecution(BatchWrite batchWrite) {
573 + batchWrite.setState(intent, WITHDRAW_REQ);
640 } 574 }
641 575
642 - Intent newIntent() { 576 + @Override
643 - return newIntent; 577 + public Optional<IntentUpdate> execute() {
578 + return Optional.of(new Withdrawing(intent, installables));
579 + }
644 } 580 }
645 581
646 - List<Intent> oldInstallables() { 582 + private class ReplaceRequest extends IntentUpdate {
647 - return oldInstallables; 583 +
584 + private final Intent newIntent;
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);
648 } 592 }
649 593
650 - List<Intent> newInstallables() { 594 + @Override
651 - return newInstallables; 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);
652 } 599 }
653 600
654 - void setInstallables(List<Intent> installables) { 601 + @Override
655 - newInstallables = installables; 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 + }
656 } 614 }
657 615
658 - boolean isComplete() { 616 + private class DoNothing extends CompletedIntentUpdate {
659 - return currentBatch >= batches.size();
660 } 617 }
661 618
662 - FlowRuleBatchOperation currentBatch() { 619 + // TODO: better naming
663 - return !isComplete() ? batches.get(currentBatch) : null; 620 + private class WithdrawStateChange1 extends CompletedIntentUpdate {
621 +
622 + private final Intent intent;
623 +
624 + WithdrawStateChange1(Intent intent) {
625 + this.intent = checkNotNull(intent);
664 } 626 }
665 627
666 - void batchSuccess() { 628 + @Override
667 - // move on to next Batch 629 + public void writeBeforeExecution(BatchWrite batchWrite) {
668 - currentBatch++; 630 + batchWrite.setState(intent, WITHDRAW_REQ);
669 } 631 }
670 632
671 - void batchFailed() { 633 + @Override
672 - // the current batch has failed, so recompile 634 + public void writeAfterExecution(BatchWrite batchWrite) {
673 - // remove the current batch and all remaining 635 + batchWrite.setState(intent, WITHDRAWN);
674 - for (int i = currentBatch; i < batches.size(); i++) { 636 + batchWrite.removeInstalledIntents(intent.id());
675 - batches.remove(i); 637 + batchWrite.removeIntent(intent.id());
676 } 638 }
677 - if (oldIntent != null) {
678 - executeWithdrawingPhase(this); // remove the old intent
679 } 639 }
680 - if (newIntent != null) { 640 +
681 - setInflightState(newIntent, FAILED); 641 + // TODO: better naming
682 - batches.addAll(uninstallIntent(newIntent, newInstallables())); 642 + private class WithdrawStateChange2 extends CompletedIntentUpdate {
643 +
644 + private final Intent intent;
645 +
646 + WithdrawStateChange2(Intent intent) {
647 + this.intent = checkNotNull(intent);
683 } 648 }
684 649
685 - // TODO we might want to try to recompile the new intent 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);
686 } 655 }
687 656
688 - private void finalizeStates(BatchWrite batchWrite) { 657 + @Override
689 - // events to be triggered on successful write 658 + public void writeAfterExecution(BatchWrite batchWrite) {
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); 659 batchWrite.setState(intent, WITHDRAWN);
698 batchWrite.removeInstalledIntents(intent.id()); 660 batchWrite.removeInstalledIntents(intent.id());
699 batchWrite.removeIntent(intent.id()); 661 batchWrite.removeIntent(intent.id());
700 - break;
701 - case FAILED:
702 - batchWrite.setState(intent, FAILED);
703 - batchWrite.removeInstalledIntents(intent.id());
704 - break;
705 -
706 - // FALLTHROUGH to default from here
707 - case INSTALL_REQ:
708 - case COMPILING:
709 - case RECOMPILING:
710 - case WITHDRAW_REQ:
711 - case WITHDRAWN:
712 - case INSTALLED:
713 - default:
714 - //FIXME clean this up (we shouldn't ever get here)
715 - log.warn("Bad state: {} for {}", getInflightState(intent), intent);
716 - break;
717 - }
718 } 662 }
719 } 663 }
720 664
721 - void addBatches(List<FlowRuleBatchOperation> batches) { 665 + private class Compiling extends IntentUpdate {
722 - this.batches.addAll(batches); 666 +
667 + private final Intent intent;
668 +
669 + Compiling(Intent intent) {
670 + this.intent = checkNotNull(intent);
723 } 671 }
724 672
725 - IntentState getInflightState(Intent intent) { 673 + @Override
726 - return stateMap.get(intent); 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));
727 } 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;
728 693
729 - // set transient state during intent update process 694 + Installing(Intent intent, List<Intent> installables) {
730 - void setInflightState(Intent intent, IntentState newState) { 695 + this.intent = checkNotNull(intent);
731 - // This method should be called for 696 + this.installables = ImmutableList.copyOf(checkNotNull(installables));
732 - // transition to non-parking or Failed only
733 - if (!NON_PARKED_OR_FAILED.contains(newState)) {
734 - log.error("Unexpected transition to {}", newState);
735 } 697 }
736 698
737 - IntentState oldState = stateMap.get(intent); 699 + @Override
738 - log.debug("intent id: {}, old state: {}, new state: {}", 700 + public Optional<IntentUpdate> execute() {
739 - intent.id(), oldState, newState); 701 + Exception exception = null;
702 + // Indicate that the intent is entering the installing phase.
740 703
741 - stateMap.put(intent, newState); 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;
742 } 715 }
743 } 716 }
744 717
745 - private class IntentInstallMonitor implements Runnable { 718 + if (exception != null) {
746 - 719 + return Optional.of(new InstallingFailed(intent, installables, batches));
747 - // TODO make this configurable through a configuration file using @Property mechanism 720 + }
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
750 - private static final int MAX_ATTEMPTS = 3;
751 721
752 - private final IntentOperations ops; 722 + return Optional.of(new Installed(intent, installables, batches));
753 - private final List<IntentUpdate> intentUpdates = Lists.newArrayList(); 723 + }
724 + }
754 725
755 - private final Duration timeoutPerOperation; 726 + private class Withdrawing extends IntentUpdate {
756 - private final int maxAttempts;
757 727
758 - // future holding current FlowRuleBatch installation result 728 + private final Intent intent;
759 - private Future<CompletedBatchOperation> future; 729 + private final List<Intent> installables;
760 - private long startTime = System.currentTimeMillis();
761 - private long endTime;
762 - private int installAttempt;
763 730
764 - public IntentInstallMonitor(IntentOperations ops) { 731 + Withdrawing(Intent intent, List<Intent> installables) {
765 - this(ops, Duration.ofMillis(TIMEOUT_PER_OP), MAX_ATTEMPTS); 732 + this.intent = checkNotNull(intent);
733 + this.installables = ImmutableList.copyOf(installables);
766 } 734 }
767 735
768 - public IntentInstallMonitor(IntentOperations ops, Duration timeoutPerOperation, int maxAttempts) { 736 + @Override
769 - this.ops = checkNotNull(ops); 737 + public Optional<IntentUpdate> execute() {
770 - this.timeoutPerOperation = checkNotNull(timeoutPerOperation); 738 + List<FlowRuleBatchOperation> batches = uninstallIntent(intent, installables);
771 - checkArgument(maxAttempts > 0, "maxAttempts must be larger than 0, but %s", maxAttempts);
772 - this.maxAttempts = maxAttempts;
773 739
774 - resetTimeoutLimit(); 740 + return Optional.of(new Withdrawn(intent, installables, batches));
775 } 741 }
776 -
777 - private void resetTimeoutLimit() {
778 - // FIXME compute reasonable timeouts
779 - this.endTime = System.currentTimeMillis()
780 - + ops.operations().size() * timeoutPerOperation.toMillis();
781 } 742 }
782 743
783 - private void buildIntentUpdates() { 744 + private class Replacing extends IntentUpdate {
784 - BatchWrite batchWrite = BatchWrite.newInstance();
785 745
786 - // create context and record new request to store 746 + private final Intent newIntent;
787 - for (IntentOperation op : ops.operations()) { 747 + private final Intent oldIntent;
788 - IntentUpdate update = new IntentUpdate(op); 748 + private final List<Intent> newInstallables;
789 - update.init(batchWrite); 749 + private final List<Intent> oldInstallables;
790 - intentUpdates.add(update);
791 - }
792 750
793 - if (!batchWrite.isEmpty()) { 751 + private Exception exception;
794 - store.batchWrite(batchWrite);
795 - }
796 752
797 - // start processing each Intents 753 + Replacing(Intent newIntent, Intent oldIntent,
798 - for (IntentUpdate update : intentUpdates) { 754 + List<Intent> newInstallables, List<Intent> oldInstallables) {
799 - processIntentUpdate(update); 755 + this.newIntent = checkNotNull(newIntent);
756 + this.oldIntent = checkNotNull(oldIntent);
757 + this.newInstallables = ImmutableList.copyOf(checkNotNull(newInstallables));
758 + this.oldInstallables = ImmutableList.copyOf(checkNotNull(oldInstallables));
800 } 759 }
801 - future = applyNextBatch(); 760 +
761 + @Override
762 + public Optional<IntentUpdate> execute() {
763 + List<FlowRuleBatchOperation> batches = replace();
764 +
765 + if (exception == null) {
766 + return Optional.of(
767 + new Replaced(newIntent, oldIntent, newInstallables, oldInstallables, batches));
802 } 768 }
803 769
804 - /** 770 + return Optional.of(
805 - * Builds and applies the next batch, and returns the future. 771 + new ReplacingFailed(newIntent, oldIntent, newInstallables, oldInstallables, batches));
806 - *
807 - * @return Future for next batch
808 - */
809 - private Future<CompletedBatchOperation> applyNextBatch() {
810 - //TODO test this. (also, maybe save this batch)
811 - FlowRuleBatchOperation batch = new FlowRuleBatchOperation(Collections.emptyList());
812 - for (IntentUpdate update : intentUpdates) {
813 - if (!update.isComplete()) {
814 - batch.addAll(update.currentBatch());
815 } 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);
816 } 798 }
817 - if (batch.size() > 0) {
818 - //FIXME apply batch might throw an exception
819 - return flowRuleService.applyBatch(batch);
820 - } else {
821 - // there are no flow rule batches; finalize the intent update
822 - BatchWrite batchWrite = BatchWrite.newInstance();
823 - for (IntentUpdate update : intentUpdates) {
824 - update.finalizeStates(batchWrite);
825 } 799 }
826 - if (!batchWrite.isEmpty()) { 800 + return batches;
827 - store.batchWrite(batchWrite);
828 } 801 }
829 - return null;
830 } 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;
831 } 817 }
832 818
833 - private void updateBatches(CompletedBatchOperation completed) { 819 + @Override
834 - if (completed.isSuccess()) { 820 + public void batchSuccess() {
835 - for (IntentUpdate update : intentUpdates) { 821 + currentBatch++;
836 - update.batchSuccess();
837 } 822 }
838 - } else {
839 - // entire batch has been reverted...
840 - log.debug("Failed items: {}", completed.failedItems());
841 - log.debug("Failed ids: {}", completed.failedIds());
842 823
843 - for (Long id : completed.failedIds()) { 824 + @Override
844 - IntentId targetId = IntentId.valueOf(id); 825 + public List<Intent> allInstallables() {
845 - for (IntentUpdate update : intentUpdates) { 826 + return installables;
846 - List<Intent> installables = Lists.newArrayList(update.newInstallables());
847 - if (update.oldInstallables() != null) {
848 - installables.addAll(update.oldInstallables());
849 } 827 }
850 - for (Intent intent : installables) { 828 +
851 - if (intent.id().equals(targetId)) { 829 + @Override
852 - update.batchFailed(); 830 + public void writeAfterExecution(BatchWrite batchWrite) {
831 + switch (intentState) {
832 + case INSTALLING:
833 + batchWrite.setState(intent, INSTALLED);
834 + batchWrite.setInstallableIntents(intent.id(), this.installables);
835 + break;
836 + case FAILED:
837 + batchWrite.setState(intent, FAILED);
838 + batchWrite.removeInstalledIntents(intent.id());
839 + break;
840 + default:
853 break; 841 break;
854 } 842 }
855 } 843 }
844 +
845 + @Override
846 + public FlowRuleBatchOperation currentBatch() {
847 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
856 } 848 }
857 - // don't increment the non-failed items, as they have been reverted. 849 +
850 + @Override
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);
858 } 856 }
857 + intentState = FAILED;
858 + batches.addAll(uninstallIntent(intent, installables));
859 +
860 + // TODO we might want to try to recompile the new intent
859 } 861 }
860 } 862 }
861 863
862 - private void abandonShip() { 864 + private class Withdrawn extends CompletedIntentUpdate {
863 - // the batch has failed 865 +
864 - // TODO: maybe we should do more? 866 + private final Intent intent;
865 - log.error("Walk the plank, matey..."); 867 + private final List<Intent> installables;
866 - future = null; 868 + private final List<FlowRuleBatchOperation> batches;
867 - batchService.removeIntentOperations(ops); 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;
868 } 876 }
869 877
870 - /** 878 + @Override
871 - * Iterate through the pending futures, and remove them when they have completed. 879 + public List<Intent> allInstallables() {
872 - */ 880 + return installables;
873 - private void processFutures() {
874 - if (future == null) {
875 - // we are done if the future is null
876 - return;
877 } 881 }
878 - try { 882 +
879 - CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS); 883 + @Override
880 - updateBatches(completed); 884 + public void batchSuccess() {
881 - future = applyNextBatch(); 885 + currentBatch++;
882 - } catch (TimeoutException | InterruptedException te) {
883 - log.trace("Installation of intents are still pending: {}", ops);
884 - } catch (ExecutionException e) {
885 - log.warn("Execution of batch failed: {}", ops, e);
886 - abandonShip();
887 } 886 }
887 +
888 + @Override
889 + public void writeAfterExecution(BatchWrite batchWrite) {
890 + batchWrite.setState(intent, WITHDRAWN);
891 + batchWrite.removeInstalledIntents(intent.id());
892 + batchWrite.removeIntent(intent.id());
888 } 893 }
889 894
890 - private void retry() { 895 + @Override
891 - log.debug("Execution timed out, retrying."); 896 + public FlowRuleBatchOperation currentBatch() {
892 - if (future.cancel(true)) { // cancel success; batch is reverted 897 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
893 - // reset the timer
894 - resetTimeoutLimit();
895 - installAttempt++;
896 - if (installAttempt == maxAttempts) {
897 - log.warn("Install request timed out: {}", ops);
898 - for (IntentUpdate update : intentUpdates) {
899 - update.batchFailed();
900 } 898 }
901 - } else if (installAttempt > maxAttempts) { 899 +
902 - abandonShip(); 900 + @Override
903 - return; 901 + public void batchFailed() {
904 - } // else just resubmit the work 902 + // the current batch has failed, so recompile
905 - future = applyNextBatch(); 903 + // remove the current batch and all remaining
906 - executor.submit(this); 904 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
905 + batches.remove(i);
906 + }
907 + batches.addAll(uninstallIntent(intent, installables));
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);
937 +
938 + return allInstallables;
939 + }
940 +
941 + @Override
942 + public void batchSuccess() {
943 + currentBatch++;
944 + }
945 +
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() {
963 + // the current batch has failed, so recompile
964 + // remove the current batch and all remaining
965 + for (int i = batches.size() - 1; i >= currentBatch; i--) {
966 + batches.remove(i);
967 + }
968 + batches.addAll(uninstallIntent(oldIntent, oldInstallables));
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);
907 } else { 990 } else {
908 - log.error("Cancelling FlowRuleBatch failed."); 991 + log.warn("Unable to compile intent {} due to:", intent.id(), exception);
909 - // FIXME 992 + }
910 - // cancel failed... batch is broken; shouldn't happen! 993 +
911 - // we could manually reverse everything 994 + return Optional.empty();
912 - // ... or just core dump and send email to Ali 995 + }
913 - abandonShip(); 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);
1044 + }
1045 + batches.addAll(uninstallIntent(intent, installables));
1046 +
1047 + // TODO we might want to try to recompile the new intent
1048 + }
1049 + }
1050 +
1051 + private class ReplacingFailed extends CompletedIntentUpdate {
1052 +
1053 + private final Intent newIntent;
1054 + private final Intent oldIntent;
1055 + private final List<Intent> newInstallables;
1056 + private final List<Intent> oldInstallables;
1057 + private final List<FlowRuleBatchOperation> batches;
1058 + private int currentBatch;
1059 +
1060 + ReplacingFailed(Intent newIntent, Intent oldIntent,
1061 + List<Intent> newInstallables, List<Intent> oldInstallables,
1062 + List<FlowRuleBatchOperation> batches) {
1063 + this.newIntent = checkNotNull(newIntent);
1064 + this.oldIntent = checkNotNull(oldIntent);
1065 + this.newInstallables = ImmutableList.copyOf(checkNotNull(newInstallables));
1066 + this.oldInstallables = ImmutableList.copyOf(checkNotNull(oldInstallables));
1067 + this.batches = new LinkedList<>(batches);
1068 + this.currentBatch = 0;
914 } 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;
1078 + }
1079 +
1080 + @Override
1081 + public void batchSuccess() {
1082 + currentBatch++;
1083 + }
1084 +
1085 + @Override
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());
1093 + }
1094 +
1095 + @Override
1096 + public FlowRuleBatchOperation currentBatch() {
1097 + return currentBatch < batches.size() ? batches.get(currentBatch) : null;
1098 + }
1099 +
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);
1106 + }
1107 + batches.addAll(uninstallIntent(oldIntent, oldInstallables));
1108 +
1109 + batches.addAll(uninstallIntent(newIntent, newInstallables));
1110 +
1111 + // TODO we might want to try to recompile the new intent
1112 + }
1113 + }
1114 +
1115 + private class IntentBatchPreprocess implements Runnable {
1116 +
1117 + // TODO make this configurable
1118 + private static final int TIMEOUT_PER_OP = 500; // ms
1119 + protected static final int MAX_ATTEMPTS = 3;
1120 +
1121 + protected final IntentOperations ops;
1122 +
1123 + // future holding current FlowRuleBatch installation result
1124 + protected final long startTime = System.currentTimeMillis();
1125 + protected final long endTime;
1126 +
1127 + private IntentBatchPreprocess(IntentOperations ops, long endTime) {
1128 + this.ops = checkNotNull(ops);
1129 + this.endTime = endTime;
1130 + }
1131 +
1132 + public IntentBatchPreprocess(IntentOperations ops) {
1133 + this(ops, System.currentTimeMillis() + ops.operations().size() * TIMEOUT_PER_OP);
915 } 1134 }
916 1135
917 - boolean isComplete() { 1136 + // FIXME compute reasonable timeouts
918 - return future == null; 1137 + protected long calculateTimeoutLimit() {
1138 + return System.currentTimeMillis() + ops.operations().size() * TIMEOUT_PER_OP;
919 } 1139 }
920 1140
921 @Override 1141 @Override
922 public void run() { 1142 public void run() {
923 try { 1143 try {
924 - if (intentUpdates.isEmpty()) {
925 // this should only be called on the first iteration 1144 // this should only be called on the first iteration
926 // note: this a "expensive", so it is not done in the constructor 1145 // note: this a "expensive", so it is not done in the constructor
927 1146
...@@ -929,9 +1148,146 @@ public class IntentManager ...@@ -929,9 +1148,146 @@ public class IntentManager
929 // - write Intents to store 1148 // - write Intents to store
930 // - process (compile, install, etc.) each Intents 1149 // - process (compile, install, etc.) each Intents
931 // - generate FlowRuleBatch for this phase 1150 // - generate FlowRuleBatch for this phase
932 - buildIntentUpdates(); 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());
1176 + }
1177 +
1178 + private BatchWrite createBatchWrite(List<IntentUpdate> updates) {
1179 + BatchWrite batchWrite = BatchWrite.newInstance();
1180 + updates.forEach(update -> update.writeBeforeExecution(batchWrite));
1181 + return batchWrite;
1182 + }
1183 +
1184 + private List<CompletedIntentUpdate> processIntentUpdates(List<IntentUpdate> updates) {
1185 + // start processing each Intents
1186 + List<CompletedIntentUpdate> completed = new ArrayList<>();
1187 + for (IntentUpdate update : updates) {
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 + }
933 } 1199 }
934 1200
1201 + return completed;
1202 + }
1203 +
1204 + protected void writeBatch(BatchWrite batchWrite) {
1205 + if (!batchWrite.isEmpty()) {
1206 + store.batchWrite(batchWrite);
1207 + }
1208 + }
1209 + }
1210 +
1211 + // TODO: better naming
1212 + private class IntentBatchApplyFirst extends IntentBatchPreprocess {
1213 +
1214 + protected final List<CompletedIntentUpdate> intentUpdates;
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();
1230 + }
1231 +
1232 + /**
1233 + * Builds and applies the next batch, and returns the future.
1234 + *
1235 + * @return Future for next batch
1236 + */
1237 + protected Future<CompletedBatchOperation> applyNextBatch(List<CompletedIntentUpdate> updates) {
1238 + //TODO test this. (also, maybe save this batch)
1239 + FlowRuleBatchOperation batch = createFlowRuleBatchOperation(updates);
1240 + if (batch.size() > 0) {
1241 + //FIXME apply batch might throw an exception
1242 + return flowRuleService.applyBatch(batch);
1243 + } else {
1244 + // there are no flow rule batches; finalize the intent update
1245 + BatchWrite batchWrite = createFinalizedBatchWrite(updates);
1246 +
1247 + writeBatch(batchWrite);
1248 + return null;
1249 + }
1250 + }
1251 +
1252 + private FlowRuleBatchOperation createFlowRuleBatchOperation(List<CompletedIntentUpdate> intentUpdates) {
1253 + FlowRuleBatchOperation batch = new FlowRuleBatchOperation(Collections.emptyList());
1254 + for (CompletedIntentUpdate update : intentUpdates) {
1255 + FlowRuleBatchOperation currentBatch = update.currentBatch();
1256 + if (currentBatch != null) {
1257 + batch.addAll(currentBatch);
1258 + }
1259 + }
1260 + return batch;
1261 + }
1262 +
1263 + private BatchWrite createFinalizedBatchWrite(List<CompletedIntentUpdate> intentUpdates) {
1264 + BatchWrite batchWrite = BatchWrite.newInstance();
1265 + for (CompletedIntentUpdate update : intentUpdates) {
1266 + update.writeAfterExecution(batchWrite);
1267 + }
1268 + return batchWrite;
1269 + }
1270 +
1271 + protected void abandonShip() {
1272 + // the batch has failed
1273 + // TODO: maybe we should do more?
1274 + log.error("Walk the plank, matey...");
1275 + future = null;
1276 + batchService.removeIntentOperations(ops);
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 {
935 // - peek if current FlowRuleBatch is complete 1291 // - peek if current FlowRuleBatch is complete
936 // -- If complete OK: 1292 // -- If complete OK:
937 // step each IntentUpdate forward 1293 // step each IntentUpdate forward
...@@ -940,16 +1296,16 @@ public class IntentManager ...@@ -940,16 +1296,16 @@ public class IntentManager
940 // -- If complete FAIL: 1296 // -- If complete FAIL:
941 // Intent which failed: transition Intent to FAILED 1297 // Intent which failed: transition Intent to FAILED
942 // Other Intents: resubmit same FlowRuleBatch for this phase 1298 // Other Intents: resubmit same FlowRuleBatch for this phase
943 - processFutures(); 1299 + Future<CompletedBatchOperation> future = processFutures();
944 - if (isComplete()) { 1300 + if (future == null) {
945 // there are no outstanding batches; we are done 1301 // there are no outstanding batches; we are done
946 batchService.removeIntentOperations(ops); 1302 batchService.removeIntentOperations(ops);
947 - } else if (endTime < System.currentTimeMillis()) { 1303 + } else if (System.currentTimeMillis() > endTime) {
948 // - cancel current FlowRuleBatch and resubmit again 1304 // - cancel current FlowRuleBatch and resubmit again
949 retry(); 1305 retry();
950 } else { 1306 } else {
951 // we are not done yet, yield the thread by resubmitting ourselves 1307 // we are not done yet, yield the thread by resubmitting ourselves
952 - executor.submit(this); 1308 + executor.submit(new IntentBatchProcessFutures(ops, intentUpdates, endTime, installAttempt, future));
953 } 1309 }
954 } catch (Exception e) { 1310 } catch (Exception e) {
955 log.error("Error submitting batches:", e); 1311 log.error("Error submitting batches:", e);
...@@ -958,6 +1314,76 @@ public class IntentManager ...@@ -958,6 +1314,76 @@ public class IntentManager
958 abandonShip(); 1314 abandonShip();
959 } 1315 }
960 } 1316 }
1317 +
1318 + /**
1319 + * Iterate through the pending futures, and remove them when they have completed.
1320 + */
1321 + private Future<CompletedBatchOperation> processFutures() {
1322 + try {
1323 + CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS);
1324 + updateBatches(completed);
1325 + return applyNextBatch(intentUpdates);
1326 + } catch (TimeoutException | InterruptedException te) {
1327 + log.trace("Installation of intents are still pending: {}", ops);
1328 + return future;
1329 + } catch (ExecutionException e) {
1330 + log.warn("Execution of batch failed: {}", ops, e);
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 + }
1358 + }
1359 + }
1360 +
1361 + private void retry() {
1362 + log.debug("Execution timed out, retrying.");
1363 + if (future.cancel(true)) { // cancel success; batch is reverted
1364 + // reset the timer
1365 + long timeLimit = calculateTimeoutLimit();
1366 + int attempts = installAttempt + 1;
1367 + if (attempts == MAX_ATTEMPTS) {
1368 + log.warn("Install request timed out: {}", ops);
1369 + for (CompletedIntentUpdate update : intentUpdates) {
1370 + update.batchFailed();
1371 + }
1372 + } else if (attempts > MAX_ATTEMPTS) {
1373 + abandonShip();
1374 + return;
1375 + } // else just resubmit the work
1376 + Future<CompletedBatchOperation> future = applyNextBatch(intentUpdates);
1377 + executor.submit(new IntentBatchProcessFutures(ops, intentUpdates, timeLimit, attempts, future));
1378 + } else {
1379 + log.error("Cancelling FlowRuleBatch failed.");
1380 + // FIXME
1381 + // cancel failed... batch is broken; shouldn't happen!
1382 + // we could manually reverse everything
1383 + // ... or just core dump and send email to Ali
1384 + abandonShip();
1385 + }
1386 + }
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 }
......