use of com.netflix.titus.common.util.tuple.Either in project titus-control-plane by Netflix.
the class AwsIamConnector method canAgentAssume.
@Override
public Mono<Void> canAgentAssume(String iamRoleName) {
return Mono.defer(() -> {
long startTime = registry.clock().wallTime();
// Check cache first
Either<Boolean, Throwable> lastCheck = canAssumeCache.getIfPresent(iamRoleName);
if (lastCheck != null) {
return lastCheck.hasValue() ? Mono.empty() : Mono.error(lastCheck.getError());
}
// Must call AWS STS service
return AwsReactorExt.<AssumeRoleRequest, AssumeRoleResult>toMono(() -> new AssumeRoleRequest().withRoleSessionName("titusIamRoleValidation").withRoleArn(iamRoleName).withDurationSeconds(MIN_ASSUMED_ROLE_DURATION_SEC), stsAgentClient::assumeRoleAsync).flatMap(response -> {
logger.debug("Assumed into: {}", iamRoleName);
canAssumeCache.put(iamRoleName, Either.ofValue(true));
connectorMetrics.success(IamConnectorMetrics.IamMethods.CanAgentAssume, startTime);
return Mono.<Void>empty();
}).onErrorMap(error -> {
logger.debug("Error: {}", error.getMessage());
connectorMetrics.failure(IamConnectorMetrics.IamMethods.CanAgentAssume, error, startTime);
String errorCode = ((AWSSecurityTokenServiceException) error).getErrorCode();
if ("AccessDenied".equals(errorCode)) {
// STS service returns access denied error with no additional clues. To get more insight we
// would have to make a call to IAM service, but this would require access to all client accounts.
IamConnectorException cannotAssumeError = IamConnectorException.iamRoleCannotAssume(iamRoleName, configuration.getDataPlaneAgentRoleArn());
canAssumeCache.put(iamRoleName, Either.ofError(cannotAssumeError));
return cannotAssumeError;
}
return IamConnectorException.iamRoleUnexpectedError(iamRoleName, error.getMessage());
});
});
}
use of com.netflix.titus.common.util.tuple.Either in project titus-control-plane by Netflix.
the class MapWithStateTransformer method call.
@Override
public Observable<R> call(Observable<T> source) {
return Observable.unsafeCreate(subscriber -> {
AtomicReference<S> lastState = new AtomicReference<>(zeroSupplier.get());
Observable<Either<T, Function<S, Pair<R, S>>>> sourceEither = source.map(Either::ofValue);
Observable<Either<T, Function<S, Pair<R, S>>>> cleanupEither = cleanupActions.map(Either::ofError);
Subscription subscription = Observable.merge(sourceEither, cleanupEither).subscribe(next -> {
Pair<R, S> result;
if (next.hasValue()) {
try {
result = transformer.apply(next.getValue(), lastState.get());
} catch (Throwable e) {
subscriber.onError(e);
return;
}
} else {
try {
Function<S, Pair<R, S>> action = next.getError();
result = action.apply(lastState.get());
} catch (Throwable e) {
subscriber.onError(e);
return;
}
}
lastState.set(result.getRight());
subscriber.onNext(result.getLeft());
}, subscriber::onError, subscriber::onCompleted);
subscriber.add(subscription);
});
}
use of com.netflix.titus.common.util.tuple.Either in project titus-control-plane by Netflix.
the class CellWebClientConnectorUtil method doGetFromCell.
/**
* Run GET operation on all cells, but only one is expected to return a result.
*/
public static <T> Either<T, WebApplicationException> doGetFromCell(CellWebClientConnector cellWebClientConnector, String path, ParameterizedTypeReference<T> type, long timeoutMs) {
List<Either<T, Throwable>> partials;
try {
partials = Flux.merge(cellWebClientConnector.getWebClients().values().stream().map(cell -> cell.get().uri(path).retrieve().bodyToMono(type).map(Either::<T, Throwable>ofValue).onErrorResume(e -> Mono.just(Either.ofError(e)))).collect(Collectors.toList())).collectList().block(Duration.ofMillis(timeoutMs));
} catch (Exception e) {
logger.error("Unexpected error: path={}, type={}", path, type, e);
return Either.ofError(new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR));
}
if (CollectionsExt.isNullOrEmpty(partials)) {
logger.error("No result from any cell: path={}, type={}", path, type);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
Throwable systemError = null;
for (Either<T, Throwable> partial : partials) {
if (partial.hasValue()) {
return Either.ofValue(partial.getValue());
}
if (partial.getError() instanceof WebClientResponseException) {
WebClientResponseException wcError = (WebClientResponseException) partial.getError();
if (!wcError.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
systemError = partial.getError();
}
} else {
systemError = partial.getError();
}
}
// the client.
return Either.ofError(systemError != null ? new WebApplicationException(systemError) : new WebApplicationException(Response.Status.NOT_FOUND));
}
use of com.netflix.titus.common.util.tuple.Either in project titus-control-plane by Netflix.
the class KubeNotificationProcessor method handlePodUpdatedEvent.
private Mono<Void> handlePodUpdatedEvent(PodEvent event, Job job, Task task) {
// This is basic sanity check. If it fails, we have a major problem with pod state.
if (event.getPod() == null || event.getPod().getStatus() == null || event.getPod().getStatus().getPhase() == null) {
logger.warn("Pod notification with pod without status or phase set: taskId={}, pod={}", task.getId(), event.getPod());
metricsNoChangesApplied.increment();
return Mono.empty();
}
PodWrapper podWrapper = new PodWrapper(event.getPod());
Optional<V1Node> node;
if (event instanceof PodUpdatedEvent) {
node = ((PodUpdatedEvent) event).getNode();
} else if (event instanceof PodDeletedEvent) {
node = ((PodDeletedEvent) event).getNode();
} else {
node = Optional.empty();
}
Either<TaskStatus, String> newTaskStatusOrError = new PodToTaskMapper(podWrapper, node, task, event instanceof PodDeletedEvent, containerResultCodeResolver, titusRuntime).getNewTaskStatus();
if (newTaskStatusOrError.hasError()) {
logger.info(newTaskStatusOrError.getError());
metricsNoChangesApplied.increment();
return Mono.empty();
}
TaskStatus newTaskStatus = newTaskStatusOrError.getValue();
if (TaskStatus.areEquivalent(task.getStatus(), newTaskStatus)) {
logger.info("Pod change notification does not change task status: taskId={}, status={}, eventSequenceNumber={}", task.getId(), newTaskStatus, event.getSequenceNumber());
} else {
logger.info("Pod notification changes task status: taskId={}, fromStatus={}, toStatus={}, eventSequenceNumber={}", task.getId(), task.getStatus(), newTaskStatus, event.getSequenceNumber());
}
// against most up to date task version.
if (!updateTaskStatus(podWrapper, newTaskStatus, node, task, true).isPresent()) {
return Mono.empty();
}
return ReactorExt.toMono(v3JobOperations.updateTask(task.getId(), current -> updateTaskStatus(podWrapper, newTaskStatus, node, current, false), V3JobOperations.Trigger.Kube, "Pod status updated from kubernetes node (k8phase='" + event.getPod().getStatus().getPhase() + "', taskState=" + task.getStatus().getState() + ")", KUBE_CALL_METADATA));
}
use of com.netflix.titus.common.util.tuple.Either in project titus-control-plane by Netflix.
the class ReactorMapWithStateTransformer method apply.
@Override
public Publisher<R> apply(Flux<T> source) {
return Flux.create(sink -> {
AtomicReference<S> lastState = new AtomicReference<>(zeroSupplier.get());
Flux<Either<T, Function<S, Pair<R, S>>>> sourceEither = source.map(Either::ofValue);
Flux<Either<T, Function<S, Pair<R, S>>>> cleanupEither = cleanupActions.map(Either::ofError);
Disposable subscription = Flux.merge(sourceEither, cleanupEither).subscribe(next -> {
Pair<R, S> result;
if (next.hasValue()) {
try {
result = transformer.apply(next.getValue(), lastState.get());
} catch (Throwable e) {
sink.error(e);
return;
}
} else {
try {
Function<S, Pair<R, S>> action = next.getError();
result = action.apply(lastState.get());
} catch (Throwable e) {
sink.error(e);
return;
}
}
lastState.set(result.getRight());
sink.next(result.getLeft());
}, sink::error, sink::complete);
sink.onCancel(subscription);
});
}
Aggregations