Search in sources :

Example 1 with MultiEngineChangeAction

use of com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction in project titus-control-plane by Netflix.

the class DefaultReconciliationFramework method changeReferenceModel.

@Override
public Observable<Void> changeReferenceModel(MultiEngineChangeAction multiEngineChangeAction, BiFunction<String, Observable<List<ModelActionHolder>>, ChangeAction> engineChangeActionFactory, String... rootEntityHolderIds) {
    Preconditions.checkArgument(rootEntityHolderIds.length > 1, "Change action for multiple engines requested, but %s root id holders provided", rootEntityHolderIds.length);
    return Observable.create(emitter -> {
        List<ReconciliationEngine<EVENT>> engines = new ArrayList<>();
        for (String id : rootEntityHolderIds) {
            ReconciliationEngine<EVENT> engine = findEngineByRootId(id).orElseThrow(() -> new IllegalArgumentException("Reconciliation engine not found: rootId=" + id));
            engines.add(engine);
        }
        List<Observable<Map<String, List<ModelActionHolder>>>> outputs = ObservableExt.propagate(multiEngineChangeAction.apply(), engines.size());
        List<Observable<Void>> engineActions = new ArrayList<>();
        for (int i = 0; i < engines.size(); i++) {
            ReconciliationEngine<EVENT> engine = engines.get(i);
            String rootId = engine.getReferenceView().getId();
            ChangeAction engineAction = engineChangeActionFactory.apply(rootId, outputs.get(i).map(r -> r.get(rootId)));
            engineActions.add(engine.changeReferenceModel(engineAction));
        }
        // Synchronize on subscription to make sure that this operation is not interleaved with concurrent
        // subscriptions for the same set or subset of the reconciliation engines. The interleaving might result
        // in a deadlock. For example with two engines engineA and engineB:
        // - multi-engine change action M1 for engineA and engineB is scheduled
        // - M1/engineA is added to its queue
        // - another multi-engine change action M2 for engineA and engineB is scheduled
        // - M2/engineB is added to its queue
        // - M1/engineB is added to its queue, and next M2/engineA
        // Executing M1 requires that both M1/engineA and M1/engineB are at the top of the queue, but in this case
        // M2/engineB is ahead of the M1/engineB. On the other hand, M1/engineA is ahead of M2/engineB. Because
        // of that we have deadlock. Please, note that we can ignore here the regular (engine scoped) change actions.
        Subscription subscription;
        synchronized (multiEngineChangeLock) {
            subscription = Observable.mergeDelayError(engineActions).subscribe(emitter::onNext, emitter::onError, emitter::onCompleted);
        }
        emitter.setSubscription(subscription);
    }, Emitter.BackpressureMode.NONE);
}
Also used : Completable(rx.Completable) BiFunction(java.util.function.BiFunction) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) ArrayList(java.util.ArrayList) Observable(rx.Observable) HashSet(java.util.HashSet) Pair(com.netflix.titus.common.util.tuple.Pair) Map(java.util.Map) ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) Schedulers(rx.schedulers.Schedulers) ExceptionExt(com.netflix.titus.common.util.ExceptionExt) ExecutorService(java.util.concurrent.ExecutorService) Logger(org.slf4j.Logger) Subscriber(rx.Subscriber) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) Set(java.util.Set) BlockingQueue(java.util.concurrent.BlockingQueue) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) Emitter(rx.Emitter) Scheduler(rx.Scheduler) LinkedBlockingQueue(java.util.concurrent.LinkedBlockingQueue) Collectors(java.util.stream.Collectors) Executors(java.util.concurrent.Executors) EntityHolder(com.netflix.titus.common.framework.reconciler.EntityHolder) TimeUnit(java.util.concurrent.TimeUnit) Timer(com.netflix.spectator.api.Timer) CountDownLatch(java.util.concurrent.CountDownLatch) ModelActionHolder(com.netflix.titus.common.framework.reconciler.ModelActionHolder) List(java.util.List) ReconciliationEngine(com.netflix.titus.common.framework.reconciler.ReconciliationEngine) Registry(com.netflix.spectator.api.Registry) ReconciliationFramework(com.netflix.titus.common.framework.reconciler.ReconciliationFramework) Optional(java.util.Optional) Preconditions(com.google.common.base.Preconditions) PolledMeter(com.netflix.spectator.api.patterns.PolledMeter) Comparator(java.util.Comparator) Collections(java.util.Collections) ReconcileEventFactory(com.netflix.titus.common.framework.reconciler.ReconcileEventFactory) ObservableExt(com.netflix.titus.common.util.rx.ObservableExt) Subscription(rx.Subscription) ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) ArrayList(java.util.ArrayList) Observable(rx.Observable) ReconciliationEngine(com.netflix.titus.common.framework.reconciler.ReconciliationEngine) ArrayList(java.util.ArrayList) List(java.util.List) Subscription(rx.Subscription)

Example 2 with MultiEngineChangeAction

use of com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction in project titus-control-plane by Netflix.

the class DefaultReconciliationFrameworkTest method testFailingMultiEngineChangeAction.

@Test
public void testFailingMultiEngineChangeAction() {
    EntityHolder root1 = EntityHolder.newRoot("myRoot1", "myEntity1");
    EntityHolder root2 = EntityHolder.newRoot("myRoot2", "myEntity2");
    framework.newEngine(root1).subscribe();
    framework.newEngine(root2).subscribe();
    testScheduler.triggerActions();
    Observable<Void> multiChangeObservable = framework.changeReferenceModel(// Keep anonymous class instead of lambda for readability
    new MultiEngineChangeAction() {

        @Override
        public Observable<Map<String, List<ModelActionHolder>>> apply() {
            return Observable.error(new RuntimeException("simulated error"));
        }
    }, // Keep anonymous class instead of lambda for readability
    (id, modelUpdates) -> new ChangeAction() {

        @Override
        public Observable<List<ModelActionHolder>> apply() {
            return modelUpdates;
        }
    }, "myRoot1", "myRoot2");
    ExtTestSubscriber<Void> multiChangeSubscriber = new ExtTestSubscriber<>();
    multiChangeObservable.subscribe(multiChangeSubscriber);
    assertThat(multiChangeSubscriber.isError()).isTrue();
    String errorMessage = ExceptionExt.toMessageChain(multiChangeSubscriber.getError());
    assertThat(errorMessage).contains("simulated error");
}
Also used : ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) ExtTestSubscriber(com.netflix.titus.testkit.rx.ExtTestSubscriber) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) EntityHolder(com.netflix.titus.common.framework.reconciler.EntityHolder) Observable(rx.Observable) List(java.util.List) ModelActionHolder(com.netflix.titus.common.framework.reconciler.ModelActionHolder) Test(org.junit.Test)

Example 3 with MultiEngineChangeAction

use of com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction in project titus-control-plane by Netflix.

the class DefaultReconciliationFrameworkTest method testMultiEngineChangeAction.

@Test
public void testMultiEngineChangeAction() {
    EntityHolder root1 = EntityHolder.newRoot("myRoot1", "myEntity1");
    EntityHolder root2 = EntityHolder.newRoot("myRoot2", "myEntity2");
    framework.newEngine(root1).subscribe();
    framework.newEngine(root2).subscribe();
    testScheduler.triggerActions();
    MultiEngineChangeAction multiEngineChangeAction = () -> Observable.just(ImmutableMap.of("myRoot1", ModelActionHolder.allModels(new SimpleModelUpdateAction(EntityHolder.newRoot("myRoot1", "myEntity1#v2"), true)), "myRoot2", ModelActionHolder.allModels(new SimpleModelUpdateAction(EntityHolder.newRoot("myRoot2", "myEntity2#v2"), true))));
    Map<String, List<ModelActionHolder>> holders = new HashMap<>();
    Observable<Void> multiChangeObservable = framework.changeReferenceModel(multiEngineChangeAction, (id, modelUpdates) -> {
        ChangeAction changeAction = () -> modelUpdates.doOnNext(next -> holders.put(id, next));
        return changeAction;
    }, "myRoot1", "myRoot2");
    verify(engine1, times(0)).changeReferenceModel(any());
    verify(engine2, times(0)).changeReferenceModel(any());
    ExtTestSubscriber<Void> multiChangeSubscriber = new ExtTestSubscriber<>();
    multiChangeObservable.subscribe(multiChangeSubscriber);
    assertThat(multiChangeSubscriber.isUnsubscribed()).isTrue();
    verify(engine1, times(1)).changeReferenceModel(any());
    verify(engine2, times(1)).changeReferenceModel(any());
    // one action per view (Running, Store, Reference)
    assertThat(holders.get("myRoot1")).hasSize(3);
    SimpleModelUpdateAction modelAction1 = (SimpleModelUpdateAction) holders.get("myRoot1").get(0).getAction();
    assertThat((String) modelAction1.getEntityHolder().getEntity()).isEqualTo("myEntity1#v2");
    // one action per view (Running, Store, Reference)
    assertThat(holders.get("myRoot2")).hasSize(3);
    SimpleModelUpdateAction modelAction2 = (SimpleModelUpdateAction) holders.get("myRoot2").get(0).getAction();
    assertThat((String) modelAction2.getEntityHolder().getEntity()).isEqualTo("myEntity2#v2");
}
Also used : ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) ExtTestSubscriber(com.netflix.titus.testkit.rx.ExtTestSubscriber) HashMap(java.util.HashMap) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) EntityHolder(com.netflix.titus.common.framework.reconciler.EntityHolder) List(java.util.List) Test(org.junit.Test)

Example 4 with MultiEngineChangeAction

use of com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction in project titus-control-plane by Netflix.

the class DefaultReconciliationFrameworkTest method testMultiEngineChangeActionWithInvalidEngineId.

@Test
public void testMultiEngineChangeActionWithInvalidEngineId() {
    EntityHolder root1 = EntityHolder.newRoot("myRoot1", "myEntity1");
    framework.newEngine(root1).subscribe();
    testScheduler.triggerActions();
    Observable<Void> multiChangeObservable = framework.changeReferenceModel(// Keep anonymous class instead of lambda for readability
    new MultiEngineChangeAction() {

        @Override
        public Observable<Map<String, List<ModelActionHolder>>> apply() {
            return Observable.error(new IllegalStateException("invocation not expected"));
        }
    }, // Keep anonymous class instead of lambda for readability
    (id, modelUpdates) -> new ChangeAction() {

        @Override
        public Observable<List<ModelActionHolder>> apply() {
            return Observable.error(new IllegalStateException("invocation not expected"));
        }
    }, "myRoot1", "badRootId");
    ExtTestSubscriber<Void> multiChangeSubscriber = new ExtTestSubscriber<>();
    multiChangeObservable.subscribe(multiChangeSubscriber);
    assertThat(multiChangeSubscriber.isError()).isTrue();
    assertThat(multiChangeSubscriber.getError().getMessage()).contains("badRootId");
}
Also used : ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) ExtTestSubscriber(com.netflix.titus.testkit.rx.ExtTestSubscriber) MultiEngineChangeAction(com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction) EntityHolder(com.netflix.titus.common.framework.reconciler.EntityHolder) Observable(rx.Observable) List(java.util.List) ModelActionHolder(com.netflix.titus.common.framework.reconciler.ModelActionHolder) Test(org.junit.Test)

Aggregations

ChangeAction (com.netflix.titus.common.framework.reconciler.ChangeAction)4 EntityHolder (com.netflix.titus.common.framework.reconciler.EntityHolder)4 MultiEngineChangeAction (com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction)4 List (java.util.List)4 ModelActionHolder (com.netflix.titus.common.framework.reconciler.ModelActionHolder)3 ExtTestSubscriber (com.netflix.titus.testkit.rx.ExtTestSubscriber)3 Test (org.junit.Test)3 Observable (rx.Observable)3 HashMap (java.util.HashMap)2 Preconditions (com.google.common.base.Preconditions)1 Registry (com.netflix.spectator.api.Registry)1 Timer (com.netflix.spectator.api.Timer)1 PolledMeter (com.netflix.spectator.api.patterns.PolledMeter)1 ReconcileEventFactory (com.netflix.titus.common.framework.reconciler.ReconcileEventFactory)1 ReconciliationEngine (com.netflix.titus.common.framework.reconciler.ReconciliationEngine)1 ReconciliationFramework (com.netflix.titus.common.framework.reconciler.ReconciliationFramework)1 ExceptionExt (com.netflix.titus.common.util.ExceptionExt)1 ObservableExt (com.netflix.titus.common.util.rx.ObservableExt)1 Pair (com.netflix.titus.common.util.tuple.Pair)1 ArrayList (java.util.ArrayList)1