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