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
Showing
1 changed file
with
819 additions
and
394 deletions
... | @@ -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 | } | ... | ... |
-
Please register or login to post a comment