use of io.strimzi.operator.common.operator.resource.ReconcileResult in project strimzi by strimzi.
the class AbstractOperator method reconcile.
/**
* Reconcile assembly resources in the given namespace having the given {@code name}.
* Reconciliation works by getting the assembly resource (e.g. {@code KafkaUser})
* in the given namespace with the given name and
* comparing with the corresponding resource.
* @param reconciliation The reconciliation.
* @return A Future which is completed with the result of the reconciliation.
*/
@Override
@SuppressWarnings("unchecked")
public final Future<Void> reconcile(Reconciliation reconciliation) {
String namespace = reconciliation.namespace();
String name = reconciliation.name();
reconciliationsCounter(reconciliation.namespace()).increment();
Timer.Sample reconciliationTimerSample = Timer.start(metrics.meterRegistry());
Future<Void> handler = withLock(reconciliation, LOCK_TIMEOUT_MS, () -> {
T cr = resourceOperator.get(namespace, name);
if (cr != null) {
if (!Util.matchesSelector(selector(), cr)) {
// When the labels matching the selector are removed from the custom resource, a DELETE event is
// triggered by the watch even through the custom resource might not match the watch labels anymore
// and might not be really deleted. We have to filter these situations out and ignore the
// reconciliation because such resource might be already operated by another instance (where the
// same change triggered ADDED event).
LOGGER.debugCr(reconciliation, "{} {} in namespace {} does not match label selector {} and will be ignored", kind(), name, namespace, selector().get().getMatchLabels());
return Future.succeededFuture();
}
Promise<Void> createOrUpdate = Promise.promise();
if (Annotations.isReconciliationPausedWithAnnotation(cr)) {
S status = createStatus();
Set<Condition> conditions = validate(reconciliation, cr);
conditions.add(StatusUtils.getPausedCondition());
status.setConditions(new ArrayList<>(conditions));
status.setObservedGeneration(cr.getStatus() != null ? cr.getStatus().getObservedGeneration() : 0);
updateStatus(reconciliation, status).onComplete(statusResult -> {
if (statusResult.succeeded()) {
createOrUpdate.complete();
} else {
createOrUpdate.fail(statusResult.cause());
}
});
pausedResourceCounter(namespace).getAndIncrement();
LOGGER.debugCr(reconciliation, "Reconciliation of {} {} is paused", kind, name);
return createOrUpdate.future();
} else if (cr.getSpec() == null) {
InvalidResourceException exception = new InvalidResourceException("Spec cannot be null");
S status = createStatus();
Condition errorCondition = new ConditionBuilder().withLastTransitionTime(StatusUtils.iso8601Now()).withType("NotReady").withStatus("True").withReason(exception.getClass().getSimpleName()).withMessage(exception.getMessage()).build();
status.setObservedGeneration(cr.getMetadata().getGeneration());
status.addCondition(errorCondition);
LOGGER.errorCr(reconciliation, "{} spec cannot be null", cr.getMetadata().getName());
updateStatus(reconciliation, status).onComplete(notUsed -> {
createOrUpdate.fail(exception);
});
return createOrUpdate.future();
}
Set<Condition> unknownAndDeprecatedConditions = validate(reconciliation, cr);
LOGGER.infoCr(reconciliation, "{} {} will be checked for creation or modification", kind, name);
createOrUpdate(reconciliation, cr).onComplete(res -> {
if (res.succeeded()) {
S status = res.result();
addWarningsToStatus(status, unknownAndDeprecatedConditions);
updateStatus(reconciliation, status).onComplete(statusResult -> {
if (statusResult.succeeded()) {
createOrUpdate.complete();
} else {
createOrUpdate.fail(statusResult.cause());
}
});
} else {
if (res.cause() instanceof ReconciliationException) {
ReconciliationException e = (ReconciliationException) res.cause();
Status status = e.getStatus();
addWarningsToStatus(status, unknownAndDeprecatedConditions);
LOGGER.errorCr(reconciliation, "createOrUpdate failed", e.getCause());
updateStatus(reconciliation, (S) status).onComplete(statusResult -> {
createOrUpdate.fail(e.getCause());
});
} else {
LOGGER.errorCr(reconciliation, "createOrUpdate failed", res.cause());
createOrUpdate.fail(res.cause());
}
}
});
return createOrUpdate.future();
} else {
LOGGER.infoCr(reconciliation, "{} {} should be deleted", kind, name);
return delete(reconciliation).map(deleteResult -> {
if (deleteResult) {
LOGGER.infoCr(reconciliation, "{} {} deleted", kind, name);
} else {
LOGGER.infoCr(reconciliation, "Assembly {} or some parts of it will be deleted by garbage collection", name);
}
return (Void) null;
}).recover(deleteResult -> {
LOGGER.errorCr(reconciliation, "Deletion of {} {} failed", kind, name, deleteResult);
return Future.failedFuture(deleteResult);
});
}
});
Promise<Void> result = Promise.promise();
handler.onComplete(reconcileResult -> {
try {
handleResult(reconciliation, reconcileResult, reconciliationTimerSample);
} finally {
result.handle(reconcileResult);
}
});
return result.future();
}
use of io.strimzi.operator.common.operator.resource.ReconcileResult in project strimzi by strimzi.
the class KafkaUserOperator method reconcileCredentialsQuotasAndAcls.
/**
* Reconciles the credentials, quotas and ACLs
*
* @param reconciliation Unique identification for the reconciliation
* @param user Model describing the KafkaUser
* @param userStatus Status subresource of the KafkaUser custom resource
*
* @return Future describing the result
*/
private CompositeFuture reconcileCredentialsQuotasAndAcls(Reconciliation reconciliation, KafkaUserModel user, KafkaUserStatus userStatus) {
Set<SimpleAclRule> tlsAcls = null;
Set<SimpleAclRule> scramOrNoneAcls = null;
KafkaUserQuotas tlsQuotas = null;
KafkaUserQuotas scramOrNoneQuotas = null;
if (user.isTlsUser() || user.isTlsExternalUser()) {
tlsAcls = user.getSimpleAclRules();
tlsQuotas = user.getQuotas();
} else if (user.isScramUser() || user.isNoneUser()) {
scramOrNoneAcls = user.getSimpleAclRules();
scramOrNoneQuotas = user.getQuotas();
}
// Reconcile the user SCRAM-SHA-512 credentials
Future<ReconcileResult<String>> scramCredentialsFuture = scramCredentialsOperator.reconcile(reconciliation, user.getName(), user.getScramSha512Password());
// Quotas need to reconciled for both regular and TLS username. It will be (possibly) set for one user and deleted for the other
Future<ReconcileResult<KafkaUserQuotas>> tlsQuotasFuture = quotasOperator.reconcile(reconciliation, KafkaUserModel.getTlsUserName(reconciliation.name()), tlsQuotas);
Future<ReconcileResult<KafkaUserQuotas>> quotasFuture = quotasOperator.reconcile(reconciliation, KafkaUserModel.getScramUserName(reconciliation.name()), scramOrNoneQuotas);
// Reconcile the user secret generated by the user operator with the credentials
Future<ReconcileResult<Secret>> userSecretFuture = reconcileUserSecret(reconciliation, user, userStatus);
// ACLs need to reconciled for both regular and TLS username. It will be (possibly) set for one user and deleted for the other
Future<ReconcileResult<Set<SimpleAclRule>>> aclsTlsUserFuture;
Future<ReconcileResult<Set<SimpleAclRule>>> aclsScramUserFuture;
if (config.isAclsAdminApiSupported()) {
aclsTlsUserFuture = aclOperations.reconcile(reconciliation, KafkaUserModel.getTlsUserName(reconciliation.name()), tlsAcls);
aclsScramUserFuture = aclOperations.reconcile(reconciliation, KafkaUserModel.getScramUserName(reconciliation.name()), scramOrNoneAcls);
} else {
aclsTlsUserFuture = Future.succeededFuture(ReconcileResult.noop(null));
aclsScramUserFuture = Future.succeededFuture(ReconcileResult.noop(null));
}
return CompositeFuture.join(scramCredentialsFuture, tlsQuotasFuture, quotasFuture, aclsTlsUserFuture, aclsScramUserFuture, userSecretFuture);
}
use of io.strimzi.operator.common.operator.resource.ReconcileResult in project strimzi by strimzi.
the class StatefulSetOperator method internalReplace.
/**
* Sometimes, patching the resource is not enough. For example when the persistent volume claim templates are modified.
* In such case we need to delete the STS with cascading=false and recreate it.
* A rolling update should done finished after the STS is recreated.
*
* @param namespace Namespace of the resource which should be deleted
* @param name Name of the resource which should be deleted
* @param current Current StatefulSet
* @param desired Desired StatefulSet
* @param cascading Defines whether the delete should be cascading or not (e.g. whether a STS deletion should delete pods etc.)
*
* @return Future with result of the reconciliation
*/
protected Future<ReconcileResult<StatefulSet>> internalReplace(Reconciliation reconciliation, String namespace, String name, StatefulSet current, StatefulSet desired, boolean cascading) {
try {
Promise<ReconcileResult<StatefulSet>> promise = Promise.promise();
long pollingIntervalMs = 1_000;
long timeoutMs = operationTimeoutMs;
operation().inNamespace(namespace).withName(name).withPropagationPolicy(cascading ? DeletionPropagation.FOREGROUND : DeletionPropagation.ORPHAN).withGracePeriod(-1L).delete();
Future<Void> deletedFut = waitFor(reconciliation, namespace, name, "deleted", pollingIntervalMs, timeoutMs, (ignore1, ignore2) -> {
StatefulSet sts = get(namespace, name);
LOGGER.traceCr(reconciliation, "Checking if {} {} in namespace {} has been deleted", resourceKind, name, namespace);
return sts == null;
});
deletedFut.onComplete(res -> {
if (res.succeeded()) {
StatefulSet result = operation().inNamespace(namespace).withName(name).create(desired);
LOGGER.debugCr(reconciliation, "{} {} in namespace {} has been replaced", resourceKind, name, namespace);
promise.complete(wasChanged(current, result) ? ReconcileResult.patched(result) : ReconcileResult.noop(result));
} else {
promise.fail(res.cause());
}
});
return promise.future();
} catch (Exception e) {
LOGGER.debugCr(reconciliation, "Caught exception while replacing {} {} in namespace {}", resourceKind, name, namespace, e);
return Future.failedFuture(e);
}
}
use of io.strimzi.operator.common.operator.resource.ReconcileResult in project strimzi-kafka-operator by strimzi.
the class AbstractConnectOperator method createConnectorWatch.
/**
* Create a watch on {@code KafkaConnector} in the given {@code namespace}.
* The watcher will:
* <ul>
* <li>{@linkplain #reconcileConnectors(Reconciliation, CustomResource, KafkaConnectStatus, boolean, String, OrderedProperties)} on the KafkaConnect
* identified by {@code KafkaConnector.metadata.labels[strimzi.io/cluster]}.</li>
* <li>The {@code KafkaConnector} status is updated with the result.</li>
* </ul>
* @param connectOperator The operator for {@code KafkaConnect}.
* @param watchNamespaceOrWildcard The namespace to watch.
* @param selectorLabels Selector labels for filtering the custom resources
*
* @return A future which completes when the watch has been set up.
*/
public static Future<Void> createConnectorWatch(AbstractConnectOperator<KubernetesClient, KafkaConnect, KafkaConnectList, Resource<KafkaConnect>, KafkaConnectSpec, KafkaConnectStatus> connectOperator, String watchNamespaceOrWildcard, Labels selectorLabels) {
Optional<LabelSelector> selector = (selectorLabels == null || selectorLabels.toMap().isEmpty()) ? Optional.empty() : Optional.of(new LabelSelector(null, selectorLabels.toMap()));
return Util.async(connectOperator.vertx, () -> {
connectOperator.connectorOperator.watch(watchNamespaceOrWildcard, new Watcher<KafkaConnector>() {
@Override
public void eventReceived(Action action, KafkaConnector kafkaConnector) {
String connectorName = kafkaConnector.getMetadata().getName();
String connectorNamespace = kafkaConnector.getMetadata().getNamespace();
String connectorKind = kafkaConnector.getKind();
String connectName = kafkaConnector.getMetadata().getLabels() == null ? null : kafkaConnector.getMetadata().getLabels().get(Labels.STRIMZI_CLUSTER_LABEL);
String connectNamespace = connectorNamespace;
switch(action) {
case ADDED:
case DELETED:
case MODIFIED:
if (connectName != null) {
// Check whether a KafkaConnect exists
connectOperator.resourceOperator.getAsync(connectNamespace, connectName).compose(connect -> {
KafkaConnectApi apiClient = connectOperator.connectClientProvider.apply(connectOperator.vertx);
if (connect == null) {
Reconciliation r = new Reconciliation("connector-watch", connectOperator.kind(), kafkaConnector.getMetadata().getNamespace(), connectName);
updateStatus(r, noConnectCluster(connectNamespace, connectName), kafkaConnector, connectOperator.connectorOperator);
LOGGER.infoCr(r, "{} {} in namespace {} was {}, but Connect cluster {} does not exist", connectorKind, connectorName, connectorNamespace, action, connectName);
return Future.succeededFuture();
} else {
// grab the lock and call reconcileConnectors()
// (i.e. short circuit doing a whole KafkaConnect reconciliation).
Reconciliation reconciliation = new Reconciliation("connector-watch", connectOperator.kind(), kafkaConnector.getMetadata().getNamespace(), connectName);
if (!Util.matchesSelector(selector, connect)) {
LOGGER.debugCr(reconciliation, "{} {} in namespace {} was {}, but Connect cluster {} does not match label selector {} and will be ignored", connectorKind, connectorName, connectorNamespace, action, connectName, selectorLabels);
return Future.succeededFuture();
} else if (connect.getSpec() != null && connect.getSpec().getReplicas() == 0) {
LOGGER.infoCr(reconciliation, "{} {} in namespace {} was {}, but Connect cluster {} has 0 replicas", connectorKind, connectorName, connectorNamespace, action, connectName);
updateStatus(reconciliation, zeroReplicas(connectNamespace, connectName), kafkaConnector, connectOperator.connectorOperator);
return Future.succeededFuture();
} else {
LOGGER.infoCr(reconciliation, "{} {} in namespace {} was {}", connectorKind, connectorName, connectorNamespace, action);
return connectOperator.withLock(reconciliation, LOCK_TIMEOUT_MS, () -> connectOperator.reconcileConnectorAndHandleResult(reconciliation, KafkaConnectResources.qualifiedServiceName(connectName, connectNamespace), apiClient, isUseResources(connect), kafkaConnector.getMetadata().getName(), action == Action.DELETED ? null : kafkaConnector).compose(reconcileResult -> {
LOGGER.infoCr(reconciliation, "reconciled");
return Future.succeededFuture(reconcileResult);
}));
}
}
});
} else {
updateStatus(new Reconciliation("connector-watch", connectOperator.kind(), kafkaConnector.getMetadata().getNamespace(), null), new InvalidResourceException("Resource lacks label '" + Labels.STRIMZI_CLUSTER_LABEL + "': No connect cluster in which to create this connector."), kafkaConnector, connectOperator.connectorOperator);
}
break;
case ERROR:
LOGGER.errorCr(new Reconciliation("connector-watch", connectorKind, connectName, connectorNamespace), "Failed {} {} in namespace {} ", connectorKind, connectorName, connectorNamespace);
break;
default:
LOGGER.errorCr(new Reconciliation("connector-watch", connectorKind, connectName, connectorNamespace), "Unknown action: {} {} in namespace {}", connectorKind, connectorName, connectorNamespace);
}
}
@Override
public void onClose(WatcherException e) {
if (e != null) {
throw new KubernetesClientException(e.getMessage());
}
}
});
return null;
});
}
use of io.strimzi.operator.common.operator.resource.ReconcileResult in project strimzi-kafka-operator by strimzi.
the class KafkaUserOperator method reconcileCredentialsQuotasAndAcls.
/**
* Reconciles the credentials, quotas and ACLs
*
* @param reconciliation Unique identification for the reconciliation
* @param user Model describing the KafkaUser
* @param userStatus Status subresource of the KafkaUser custom resource
*
* @return Future describing the result
*/
private CompositeFuture reconcileCredentialsQuotasAndAcls(Reconciliation reconciliation, KafkaUserModel user, KafkaUserStatus userStatus) {
Set<SimpleAclRule> tlsAcls = null;
Set<SimpleAclRule> scramOrNoneAcls = null;
KafkaUserQuotas tlsQuotas = null;
KafkaUserQuotas scramOrNoneQuotas = null;
if (user.isTlsUser() || user.isTlsExternalUser()) {
tlsAcls = user.getSimpleAclRules();
tlsQuotas = user.getQuotas();
} else if (user.isScramUser() || user.isNoneUser()) {
scramOrNoneAcls = user.getSimpleAclRules();
scramOrNoneQuotas = user.getQuotas();
}
// Reconcile the user SCRAM-SHA-512 credentials
Future<ReconcileResult<String>> scramCredentialsFuture = scramCredentialsOperator.reconcile(reconciliation, user.getName(), user.getScramSha512Password());
// Quotas need to reconciled for both regular and TLS username. It will be (possibly) set for one user and deleted for the other
Future<ReconcileResult<KafkaUserQuotas>> tlsQuotasFuture = quotasOperator.reconcile(reconciliation, KafkaUserModel.getTlsUserName(reconciliation.name()), tlsQuotas);
Future<ReconcileResult<KafkaUserQuotas>> quotasFuture = quotasOperator.reconcile(reconciliation, KafkaUserModel.getScramUserName(reconciliation.name()), scramOrNoneQuotas);
// Reconcile the user secret generated by the user operator with the credentials
Future<ReconcileResult<Secret>> userSecretFuture = reconcileUserSecret(reconciliation, user, userStatus);
// ACLs need to reconciled for both regular and TLS username. It will be (possibly) set for one user and deleted for the other
Future<ReconcileResult<Set<SimpleAclRule>>> aclsTlsUserFuture;
Future<ReconcileResult<Set<SimpleAclRule>>> aclsScramUserFuture;
if (config.isAclsAdminApiSupported()) {
aclsTlsUserFuture = aclOperations.reconcile(reconciliation, KafkaUserModel.getTlsUserName(reconciliation.name()), tlsAcls);
aclsScramUserFuture = aclOperations.reconcile(reconciliation, KafkaUserModel.getScramUserName(reconciliation.name()), scramOrNoneAcls);
} else {
aclsTlsUserFuture = Future.succeededFuture(ReconcileResult.noop(null));
aclsScramUserFuture = Future.succeededFuture(ReconcileResult.noop(null));
}
return CompositeFuture.join(scramCredentialsFuture, tlsQuotasFuture, quotasFuture, aclsTlsUserFuture, aclsScramUserFuture, userSecretFuture);
}
Aggregations