Search in sources :

Example 1 with ChangeAction

use of com.netflix.titus.common.framework.reconciler.ChangeAction 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 ChangeAction

use of com.netflix.titus.common.framework.reconciler.ChangeAction 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 ChangeAction

use of com.netflix.titus.common.framework.reconciler.ChangeAction 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 ChangeAction

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

the class ServiceDifferenceResolver method findMissingRunningTasks.

/**
 * Check that for each reference job task, there is a corresponding running task.
 */
private List<ChangeAction> findMissingRunningTasks(ReconciliationEngine<JobManagerReconcilerEvent> engine, ServiceJobView refJobView, ServiceJobView runningJobView) {
    List<ChangeAction> missingTasks = new ArrayList<>();
    List<ServiceJobTask> tasks = refJobView.getTasks();
    for (ServiceJobTask refTask : tasks) {
        ServiceJobTask runningTask = runningJobView.getTaskById(refTask.getId());
        if (runtime.getComputeProvider().isReadyForScheduling()) {
            // TODO This complexity exists due to the way Fenzo is initialized on bootstrap. This code can be simplified one we move off Fenzo.
            if (runningTask == null || (refTask.getStatus().getState() == TaskState.Accepted && !TaskStatus.hasPod(refTask))) {
                missingTasks.add(BasicTaskActions.launchTaskInKube(configuration, runtime, engine, refJobView.getJob(), refTask, RECONCILER_CALLMETADATA.toBuilder().withCallReason("Launching task in Kube").build(), versionSupplier, titusRuntime));
            }
        }
    }
    return missingTasks;
}
Also used : TitusChangeAction(com.netflix.titus.master.jobmanager.service.common.action.TitusChangeAction) ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) ArrayList(java.util.ArrayList) ServiceJobTask(com.netflix.titus.api.jobmanager.model.job.ServiceJobTask)

Example 5 with ChangeAction

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

the class BatchDifferenceResolver method findMissingRunningTasks.

/**
 * Check that for each reference job task, there is a corresponding running task.
 */
private List<ChangeAction> findMissingRunningTasks(ReconciliationEngine<JobManagerReconcilerEvent> engine, BatchJobView refJobView, BatchJobView runningJobView) {
    List<ChangeAction> missingTasks = new ArrayList<>();
    List<BatchJobTask> tasks = refJobView.getTasks();
    for (BatchJobTask refTask : tasks) {
        BatchJobTask runningTask = runningJobView.getTaskById(refTask.getId());
        if (runtime.getComputeProvider().isReadyForScheduling()) {
            // TODO This complexity exists due to the way Fenzo is initialized on bootstrap. This code can be simplified one we move off Fenzo.
            if (runningTask == null || (refTask.getStatus().getState() == TaskState.Accepted && !TaskStatus.hasPod(refTask))) {
                missingTasks.add(BasicTaskActions.launchTaskInKube(configuration, runtime, engine, runningJobView.getJob(), refTask, RECONCILER_CALLMETADATA.toBuilder().withCallReason("Launching task in Kube").build(), versionSupplier, titusRuntime));
            }
        }
    }
    return missingTasks;
}
Also used : TitusChangeAction(com.netflix.titus.master.jobmanager.service.common.action.TitusChangeAction) ChangeAction(com.netflix.titus.common.framework.reconciler.ChangeAction) ArrayList(java.util.ArrayList) BatchJobTask(com.netflix.titus.api.jobmanager.model.job.BatchJobTask)

Aggregations

ChangeAction (com.netflix.titus.common.framework.reconciler.ChangeAction)17 ArrayList (java.util.ArrayList)13 EntityHolder (com.netflix.titus.common.framework.reconciler.EntityHolder)12 TitusChangeAction (com.netflix.titus.master.jobmanager.service.common.action.TitusChangeAction)10 List (java.util.List)7 Task (com.netflix.titus.api.jobmanager.model.job.Task)5 ModelActionHolder (com.netflix.titus.common.framework.reconciler.ModelActionHolder)5 MultiEngineChangeAction (com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction)5 Observable (rx.Observable)5 TaskState (com.netflix.titus.api.jobmanager.model.job.TaskState)4 ServiceJobExt (com.netflix.titus.api.jobmanager.model.job.ext.ServiceJobExt)4 CallMetadata (com.netflix.titus.api.model.callmetadata.CallMetadata)4 ReconciliationEngine (com.netflix.titus.common.framework.reconciler.ReconciliationEngine)4 HashSet (java.util.HashSet)4 Optional (java.util.Optional)4 Set (java.util.Set)4 Job (com.netflix.titus.api.jobmanager.model.job.Job)3 JobFunctions (com.netflix.titus.api.jobmanager.model.job.JobFunctions)3 JobState (com.netflix.titus.api.jobmanager.model.job.JobState)3 TaskStatus (com.netflix.titus.api.jobmanager.model.job.TaskStatus)3