use of com.netflix.titus.common.framework.reconciler.EntityHolder in project titus-control-plane by Netflix.
the class DefaultReconciliationEngine method startNextReferenceChangeAction.
private boolean startNextReferenceChangeAction() {
try {
ChangeActionHolder actionHolder;
List<Transaction> transactions = new ArrayList<>();
List<EntityHolder> changePoints = new ArrayList<>();
while ((actionHolder = referenceChangeActions.peek()) != null) {
// Ignore all unsubscribed actions
Subscriber<Void> subscriber = actionHolder.getSubscriber();
if (subscriber.isUnsubscribed()) {
referenceChangeActions.poll();
continue;
}
// Emit errors if the change point (EntityHolder for the action) not found
Optional<EntityHolder> changePointOpt = modelHolder.getReference().findById(actionHolder.getEntityHolderId());
if (!changePointOpt.isPresent()) {
referenceChangeActions.poll();
transactions.add(new FailedTransaction<>(this, actionHolder, new IllegalStateException("Entity holder not found: id=" + actionHolder.getEntityHolderId())));
continue;
}
// Check if the current item overlaps with the already taken actions
EntityHolder changePoint = changePointOpt.get();
if (!changePoints.isEmpty() && isOverlapping(changePoint, changePoints)) {
break;
}
// Create transaction
changePoints.add(changePoint);
Transaction transaction;
try {
transaction = new SingleTransaction<>(this, actionHolder.getChangeAction(), actionHolder.getCreateTimestamp(), Optional.of(actionHolder.getSubscriber()), actionHolder.getTransactionId(), false);
} catch (Exception e) {
transaction = new FailedTransaction<>(this, actionHolder, e);
}
transactions.add(transaction);
referenceChangeActions.poll();
}
if (transactions.isEmpty()) {
return false;
}
pendingTransaction = transactions.size() == 1 ? transactions.get(0) : new CompositeTransaction(transactions);
return true;
} finally {
metrics.updateChangeActionQueueSize(referenceChangeActions.size());
}
}
use of com.netflix.titus.common.framework.reconciler.EntityHolder in project titus-control-plane by Netflix.
the class DefaultReconciliationFramework method findEngineByChildId.
@Override
public Optional<Pair<ReconciliationEngine<EVENT>, EntityHolder>> findEngineByChildId(String childId) {
InternalReconciliationEngine<EVENT> engine = idToEngineMapRef.get().get(childId);
if (engine == null) {
return Optional.empty();
}
EntityHolder rootHolder = engine.getReferenceView();
if (rootHolder.getId().equals(childId)) {
return Optional.empty();
}
return rootHolder.findChildById(childId).map(c -> Pair.of(engine, c));
}
use of com.netflix.titus.common.framework.reconciler.EntityHolder 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));
}
use of com.netflix.titus.common.framework.reconciler.EntityHolder 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");
}
use of com.netflix.titus.common.framework.reconciler.EntityHolder 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");
}
Aggregations