Search in sources :

Example 1 with ModelActionHolder

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

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

the class SingleTransaction method applyModelUpdates.

@Override
public Optional<ModelHolder> applyModelUpdates(ModelHolder modelHolder) {
    if (transactionStep != TransactionStep.ChangeActionCompleted) {
        return Optional.empty();
    }
    this.transactionStep = TransactionStep.ModelsUpdated;
    if (modelActionHolders.isEmpty()) {
        return Optional.empty();
    }
    EntityHolder referenceRootHolder = modelHolder.getReference();
    EntityHolder runningRootHolder = modelHolder.getRunning();
    EntityHolder storeRootHolder = modelHolder.getStore();
    try {
        for (ModelActionHolder updateAction : modelActionHolders) {
            switch(updateAction.getModel()) {
                case Reference:
                    referenceRootHolder = applyModelUpdate(updateAction, referenceRootHolder).orElse(referenceRootHolder);
                    break;
                case Running:
                    runningRootHolder = applyModelUpdate(updateAction, runningRootHolder).orElse(runningRootHolder);
                    break;
                case Store:
                    storeRootHolder = applyModelUpdate(updateAction, storeRootHolder).orElse(storeRootHolder);
                    break;
            }
        }
    } catch (Exception e) {
        String message = String.format("Change action failure during model update for %s (%s)", referenceRootHolder.getId(), e.toString());
        logger.warn(message, e);
        engine.getTitusRuntime().getCodeInvariants().unexpectedError(message, e);
        this.changeActionError = e;
        this.transactionStep = TransactionStep.ChangeActionFailed;
        return Optional.empty();
    }
    return Optional.of(new ModelHolder(referenceRootHolder, runningRootHolder, storeRootHolder));
}
Also used : EntityHolder(com.netflix.titus.common.framework.reconciler.EntityHolder) ModelActionHolder(com.netflix.titus.common.framework.reconciler.ModelActionHolder)

Example 3 with ModelActionHolder

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

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

the class JobTransactionLogger method logJobModelUpdateErrorReconcilerEvent.

private static String logJobModelUpdateErrorReconcilerEvent(JobModelReconcilerEvent.JobModelUpdateErrorReconcilerEvent event) {
    String jobId = event.getJob().getId();
    String entityId = event.getPreviousEntityHolder().getId();
    ModelActionHolder actionHolder = event.getModelActionHolder();
    TitusModelAction action = (TitusModelAction) actionHolder.getAction();
    return doFormat(jobId, event.getTransactionId(), "error", "modelUpdate/" + event.getModelActionHolder().getModel().name(), ((TitusModelAction) actionHolder.getAction()).getName(), event.getChangeAction().getTrigger(), toTargetName(jobId, entityId), entityId, 0, 0, event.getCallMetadata().getCallerId(), event.getCallMetadata().getCallReason(), action.getSummary());
}
Also used : TitusModelAction(com.netflix.titus.master.jobmanager.service.common.action.TitusModelAction) ModelActionHolder(com.netflix.titus.common.framework.reconciler.ModelActionHolder)

Example 5 with ModelActionHolder

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

the class MoveTaskBetweenJobsAction method createModelUpdateActionsFrom.

private List<ModelActionHolder> createModelUpdateActionsFrom(Job<ServiceJobExt> updatedJobFrom, Job<ServiceJobExt> updatedJobTo, Task taskFrom, CallMetadata callMetadata) {
    List<ModelActionHolder> actions = new ArrayList<>();
    // Remove task from all models.
    TitusModelAction removeTaskAction = TitusModelAction.newModelUpdate("moveTask").job(updatedJobFrom).trigger(Trigger.API).summary("Task moved to another job: jobTo=" + updatedJobTo.getId()).callMetadata(callMetadata).removeTask(taskFrom);
    actions.addAll(ModelActionHolder.allModels(removeTaskAction));
    String summary = "Decremented the desired job size by one, as its task was moved to another job: jobTo=" + updatedJobTo.getId();
    // Change job size
    TitusModelAction modelAction = TitusModelAction.newModelUpdate("decrementJobSize").job(updatedJobFrom).trigger(Trigger.API).summary(summary).jobUpdate(jobHolder -> jobHolder.setEntity(updatedJobFrom));
    actions.addAll(ModelActionHolder.referenceAndStore(modelAction));
    return actions;
}
Also used : TitusModelAction(com.netflix.titus.master.jobmanager.service.common.action.TitusModelAction) ArrayList(java.util.ArrayList) ModelActionHolder(com.netflix.titus.common.framework.reconciler.ModelActionHolder)

Aggregations

ModelActionHolder (com.netflix.titus.common.framework.reconciler.ModelActionHolder)17 EntityHolder (com.netflix.titus.common.framework.reconciler.EntityHolder)11 TitusModelAction (com.netflix.titus.master.jobmanager.service.common.action.TitusModelAction)11 ArrayList (java.util.ArrayList)10 Task (com.netflix.titus.api.jobmanager.model.job.Task)4 ChangeAction (com.netflix.titus.common.framework.reconciler.ChangeAction)4 List (java.util.List)4 Test (org.junit.Test)4 Observable (rx.Observable)4 MultiEngineChangeAction (com.netflix.titus.common.framework.reconciler.MultiEngineChangeAction)3 Optional (java.util.Optional)3 Job (com.netflix.titus.api.jobmanager.model.job.Job)2 JobState (com.netflix.titus.api.jobmanager.model.job.JobState)2 JobStatus (com.netflix.titus.api.jobmanager.model.job.JobStatus)2 TaskStatus (com.netflix.titus.api.jobmanager.model.job.TaskStatus)2 CallMetadata (com.netflix.titus.api.model.callmetadata.CallMetadata)2 ReconciliationEngine (com.netflix.titus.common.framework.reconciler.ReconciliationEngine)2 TitusChangeAction (com.netflix.titus.master.jobmanager.service.common.action.TitusChangeAction)2 JobManagerReconcilerEvent (com.netflix.titus.master.jobmanager.service.event.JobManagerReconcilerEvent)2 ExtTestSubscriber (com.netflix.titus.testkit.rx.ExtTestSubscriber)2