use of io.strimzi.operator.common.Reconciliation in project strimzi by strimzi.
the class KafkaMirrorMaker2AssemblyOperator method createOrUpdate.
@Override
protected Future<KafkaMirrorMaker2Status> createOrUpdate(Reconciliation reconciliation, KafkaMirrorMaker2 kafkaMirrorMaker2) {
KafkaMirrorMaker2Cluster mirrorMaker2Cluster;
KafkaMirrorMaker2Status kafkaMirrorMaker2Status = new KafkaMirrorMaker2Status();
try {
mirrorMaker2Cluster = KafkaMirrorMaker2Cluster.fromCrd(reconciliation, kafkaMirrorMaker2, versions);
} catch (Exception e) {
LOGGER.warnCr(reconciliation, e);
StatusUtils.setStatusConditionAndObservedGeneration(kafkaMirrorMaker2, kafkaMirrorMaker2Status, Future.failedFuture(e));
return Future.failedFuture(new ReconciliationException(kafkaMirrorMaker2Status, e));
}
Promise<KafkaMirrorMaker2Status> createOrUpdatePromise = Promise.promise();
String namespace = reconciliation.namespace();
Map<String, String> annotations = new HashMap<>(1);
final AtomicReference<String> desiredLogging = new AtomicReference<>();
boolean mirrorMaker2HasZeroReplicas = mirrorMaker2Cluster.getReplicas() == 0;
String initCrbName = KafkaMirrorMaker2Resources.initContainerClusterRoleBindingName(kafkaMirrorMaker2.getMetadata().getName(), namespace);
ClusterRoleBinding initCrb = mirrorMaker2Cluster.generateClusterRoleBinding();
LOGGER.debugCr(reconciliation, "Updating Kafka MirrorMaker 2.0 cluster");
connectServiceAccount(reconciliation, namespace, KafkaMirrorMaker2Resources.serviceAccountName(mirrorMaker2Cluster.getCluster()), mirrorMaker2Cluster).compose(i -> connectInitClusterRoleBinding(reconciliation, initCrbName, initCrb)).compose(i -> connectNetworkPolicy(reconciliation, namespace, mirrorMaker2Cluster, true)).compose(i -> deploymentOperations.scaleDown(reconciliation, namespace, mirrorMaker2Cluster.getName(), mirrorMaker2Cluster.getReplicas())).compose(i -> serviceOperations.reconcile(reconciliation, namespace, mirrorMaker2Cluster.getServiceName(), mirrorMaker2Cluster.generateService())).compose(i -> generateMetricsAndLoggingConfigMap(reconciliation, namespace, mirrorMaker2Cluster)).compose(logAndMetricsConfigMap -> {
String logging = logAndMetricsConfigMap.getData().get(AbstractModel.ANCILLARY_CM_KEY_LOG_CONFIG);
annotations.put(Annotations.ANNO_STRIMZI_LOGGING_DYNAMICALLY_UNCHANGEABLE_HASH, Util.hashStub(Util.getLoggingDynamicallyUnmodifiableEntries(logging)));
desiredLogging.set(logging);
return configMapOperations.reconcile(reconciliation, namespace, mirrorMaker2Cluster.getAncillaryConfigMapName(), logAndMetricsConfigMap);
}).compose(i -> kafkaConnectJmxSecret(reconciliation, namespace, mirrorMaker2Cluster.getName(), mirrorMaker2Cluster)).compose(i -> pfa.hasPodDisruptionBudgetV1() ? podDisruptionBudgetOperator.reconcile(reconciliation, namespace, mirrorMaker2Cluster.getName(), mirrorMaker2Cluster.generatePodDisruptionBudget()) : Future.succeededFuture()).compose(i -> !pfa.hasPodDisruptionBudgetV1() ? podDisruptionBudgetV1Beta1Operator.reconcile(reconciliation, namespace, mirrorMaker2Cluster.getName(), mirrorMaker2Cluster.generatePodDisruptionBudgetV1Beta1()) : Future.succeededFuture()).compose(i -> generateAuthHash(namespace, kafkaMirrorMaker2.getSpec())).compose(hash -> {
if (hash != null) {
annotations.put(Annotations.ANNO_STRIMZI_AUTH_HASH, Integer.toString(hash));
}
Deployment deployment = mirrorMaker2Cluster.generateDeployment(annotations, pfa.isOpenshift(), imagePullPolicy, imagePullSecrets);
return deploymentOperations.reconcile(reconciliation, namespace, mirrorMaker2Cluster.getName(), deployment);
}).compose(i -> deploymentOperations.scaleUp(reconciliation, namespace, mirrorMaker2Cluster.getName(), mirrorMaker2Cluster.getReplicas())).compose(i -> deploymentOperations.waitForObserved(reconciliation, namespace, mirrorMaker2Cluster.getName(), 1_000, operationTimeoutMs)).compose(i -> mirrorMaker2HasZeroReplicas ? Future.succeededFuture() : deploymentOperations.readiness(reconciliation, namespace, mirrorMaker2Cluster.getName(), 1_000, operationTimeoutMs)).compose(i -> mirrorMaker2HasZeroReplicas ? Future.succeededFuture() : reconcileConnectors(reconciliation, kafkaMirrorMaker2, mirrorMaker2Cluster, kafkaMirrorMaker2Status, desiredLogging.get())).map((Void) null).onComplete(reconciliationResult -> {
List<Condition> conditions = kafkaMirrorMaker2Status.getConditions();
StatusUtils.setStatusConditionAndObservedGeneration(kafkaMirrorMaker2, kafkaMirrorMaker2Status, reconciliationResult);
if (!mirrorMaker2HasZeroReplicas) {
kafkaMirrorMaker2Status.setUrl(KafkaMirrorMaker2Resources.url(mirrorMaker2Cluster.getCluster(), namespace, KafkaMirrorMaker2Cluster.REST_API_PORT));
}
if (conditions != null && !conditions.isEmpty()) {
kafkaMirrorMaker2Status.addConditions(conditions);
}
kafkaMirrorMaker2Status.setReplicas(mirrorMaker2Cluster.getReplicas());
kafkaMirrorMaker2Status.setLabelSelector(mirrorMaker2Cluster.getSelectorLabels().toSelectorString());
if (reconciliationResult.succeeded()) {
createOrUpdatePromise.complete(kafkaMirrorMaker2Status);
} else {
createOrUpdatePromise.fail(new ReconciliationException(kafkaMirrorMaker2Status, reconciliationResult.cause()));
}
});
return createOrUpdatePromise.future();
}
use of io.strimzi.operator.common.Reconciliation in project strimzi by strimzi.
the class KafkaMirrorMaker2AssemblyOperator method reconcileConnectors.
/**
* Reconcile all the MirrorMaker 2.0 connectors selected by the given MirrorMaker 2.0 instance.
* @param reconciliation The reconciliation
* @param kafkaMirrorMaker2 The MirrorMaker 2.0
* @return A future, failed if any of the connectors could not be reconciled.
*/
protected Future<Void> reconcileConnectors(Reconciliation reconciliation, KafkaMirrorMaker2 kafkaMirrorMaker2, KafkaMirrorMaker2Cluster mirrorMaker2Cluster, KafkaMirrorMaker2Status mirrorMaker2Status, String desiredLogging) {
String mirrorMaker2Name = kafkaMirrorMaker2.getMetadata().getName();
if (kafkaMirrorMaker2.getSpec() == null) {
return maybeUpdateMirrorMaker2Status(reconciliation, kafkaMirrorMaker2, new InvalidResourceException("spec property is required"));
}
List<KafkaMirrorMaker2MirrorSpec> mirrors = ModelUtils.asListOrEmptyList(kafkaMirrorMaker2.getSpec().getMirrors());
String host = KafkaMirrorMaker2Resources.qualifiedServiceName(mirrorMaker2Name, reconciliation.namespace());
KafkaConnectApi apiClient = getKafkaConnectApi();
return apiClient.list(host, KafkaConnectCluster.REST_API_PORT).compose(deleteMirrorMaker2ConnectorNames -> {
for (Map.Entry<String, Function<KafkaMirrorMaker2MirrorSpec, KafkaMirrorMaker2ConnectorSpec>> connectorEntry : MIRRORMAKER2_CONNECTORS.entrySet()) {
deleteMirrorMaker2ConnectorNames.removeAll(mirrors.stream().filter(// filter out non-existent connectors
mirror -> connectorEntry.getValue().apply(mirror) != null).map(mirror -> mirror.getSourceCluster() + "->" + mirror.getTargetCluster() + connectorEntry.getKey()).collect(Collectors.toSet()));
}
LOGGER.debugCr(reconciliation, "delete MirrorMaker 2.0 connectors: {}", deleteMirrorMaker2ConnectorNames);
Stream<Future<Void>> deletionFutures = deleteMirrorMaker2ConnectorNames.stream().map(connectorName -> apiClient.delete(reconciliation, host, KafkaConnectCluster.REST_API_PORT, connectorName));
Stream<Future<Void>> createUpdateFutures = mirrors.stream().map(mirror -> reconcileMirrorMaker2Connectors(reconciliation, host, apiClient, kafkaMirrorMaker2, mirror, mirrorMaker2Cluster, mirrorMaker2Status, desiredLogging));
return CompositeFuture.join(Stream.concat(deletionFutures, createUpdateFutures).collect(Collectors.toList())).map((Void) null);
});
}
use of io.strimzi.operator.common.Reconciliation in project strimzi by strimzi.
the class KafkaMirrorMakerAssemblyOperator method createOrUpdate.
@Override
protected Future<KafkaMirrorMakerStatus> createOrUpdate(Reconciliation reconciliation, KafkaMirrorMaker assemblyResource) {
String namespace = reconciliation.namespace();
KafkaMirrorMakerCluster mirror;
KafkaMirrorMakerStatus kafkaMirrorMakerStatus = new KafkaMirrorMakerStatus();
try {
mirror = KafkaMirrorMakerCluster.fromCrd(reconciliation, assemblyResource, versions);
} catch (Exception e) {
LOGGER.warnCr(reconciliation, e);
StatusUtils.setStatusConditionAndObservedGeneration(assemblyResource, kafkaMirrorMakerStatus, Future.failedFuture(e));
return Future.failedFuture(new ReconciliationException(kafkaMirrorMakerStatus, e));
}
Map<String, String> annotations = new HashMap<>(1);
KafkaClientAuthentication authConsumer = assemblyResource.getSpec().getConsumer().getAuthentication();
List<CertSecretSource> trustedCertificatesConsumer = assemblyResource.getSpec().getConsumer().getTls() == null ? Collections.emptyList() : assemblyResource.getSpec().getConsumer().getTls().getTrustedCertificates();
KafkaClientAuthentication authProducer = assemblyResource.getSpec().getProducer().getAuthentication();
List<CertSecretSource> trustedCertificatesProducer = assemblyResource.getSpec().getProducer().getTls() == null ? Collections.emptyList() : assemblyResource.getSpec().getProducer().getTls().getTrustedCertificates();
Promise<KafkaMirrorMakerStatus> createOrUpdatePromise = Promise.promise();
boolean mirrorHasZeroReplicas = mirror.getReplicas() == 0;
LOGGER.debugCr(reconciliation, "Updating Kafka Mirror Maker cluster");
mirrorMakerServiceAccount(reconciliation, namespace, mirror).compose(i -> deploymentOperations.scaleDown(reconciliation, namespace, mirror.getName(), mirror.getReplicas())).compose(i -> Util.metricsAndLogging(reconciliation, configMapOperations, namespace, mirror.getLogging(), mirror.getMetricsConfigInCm())).compose(metricsAndLoggingCm -> {
ConfigMap logAndMetricsConfigMap = mirror.generateMetricsAndLogConfigMap(metricsAndLoggingCm);
annotations.put(Annotations.STRIMZI_LOGGING_ANNOTATION, logAndMetricsConfigMap.getData().get(mirror.ANCILLARY_CM_KEY_LOG_CONFIG));
return configMapOperations.reconcile(reconciliation, namespace, mirror.getAncillaryConfigMapName(), logAndMetricsConfigMap);
}).compose(i -> pfa.hasPodDisruptionBudgetV1() ? podDisruptionBudgetOperator.reconcile(reconciliation, namespace, mirror.getName(), mirror.generatePodDisruptionBudget()) : Future.succeededFuture()).compose(i -> !pfa.hasPodDisruptionBudgetV1() ? podDisruptionBudgetV1Beta1Operator.reconcile(reconciliation, namespace, mirror.getName(), mirror.generatePodDisruptionBudgetV1Beta1()) : Future.succeededFuture()).compose(i -> CompositeFuture.join(Util.authTlsHash(secretOperations, namespace, authConsumer, trustedCertificatesConsumer), Util.authTlsHash(secretOperations, namespace, authProducer, trustedCertificatesProducer))).compose(hashFut -> {
if (hashFut != null) {
annotations.put(Annotations.ANNO_STRIMZI_AUTH_HASH, Integer.toString((int) hashFut.resultAt(0) + (int) hashFut.resultAt(1)));
}
return Future.succeededFuture();
}).compose(i -> deploymentOperations.reconcile(reconciliation, namespace, mirror.getName(), mirror.generateDeployment(annotations, pfa.isOpenshift(), imagePullPolicy, imagePullSecrets))).compose(i -> deploymentOperations.scaleUp(reconciliation, namespace, mirror.getName(), mirror.getReplicas())).compose(i -> deploymentOperations.waitForObserved(reconciliation, namespace, mirror.getName(), 1_000, operationTimeoutMs)).compose(i -> mirrorHasZeroReplicas ? Future.succeededFuture() : deploymentOperations.readiness(reconciliation, namespace, mirror.getName(), 1_000, operationTimeoutMs)).onComplete(reconciliationResult -> {
StatusUtils.setStatusConditionAndObservedGeneration(assemblyResource, kafkaMirrorMakerStatus, reconciliationResult);
kafkaMirrorMakerStatus.setReplicas(mirror.getReplicas());
kafkaMirrorMakerStatus.setLabelSelector(mirror.getSelectorLabels().toSelectorString());
if (reconciliationResult.succeeded()) {
createOrUpdatePromise.complete(kafkaMirrorMakerStatus);
} else {
createOrUpdatePromise.fail(new ReconciliationException(kafkaMirrorMakerStatus, reconciliationResult.cause()));
}
});
return createOrUpdatePromise.future();
}
use of io.strimzi.operator.common.Reconciliation in project strimzi by strimzi.
the class KafkaRebalanceAssemblyOperator method requestRebalance.
private Future<MapAndStatus<ConfigMap, KafkaRebalanceStatus>> requestRebalance(Reconciliation reconciliation, String host, CruiseControlApi apiClient, KafkaRebalance kafkaRebalance, boolean dryrun, AbstractRebalanceOptions.AbstractRebalanceOptionsBuilder<?, ?> rebalanceOptionsBuilder, String userTaskID) {
LOGGER.infoCr(reconciliation, "Requesting Cruise Control rebalance [dryrun={}]", dryrun);
rebalanceOptionsBuilder.withVerboseResponse();
if (!dryrun) {
rebalanceOptionsBuilder.withFullRun();
}
// backward compatibility, no mode specified means "full"
KafkaRebalanceMode mode = Optional.ofNullable(kafkaRebalance.getSpec()).map(spec -> spec.getMode()).orElse(KafkaRebalanceMode.FULL);
Future<CruiseControlRebalanceResponse> future;
switch(mode) {
case ADD_BROKERS:
future = apiClient.addBroker(host, CruiseControl.REST_API_PORT, ((AddBrokerOptions.AddBrokerOptionsBuilder) rebalanceOptionsBuilder).build(), userTaskID);
break;
case REMOVE_BROKERS:
future = apiClient.removeBroker(host, CruiseControl.REST_API_PORT, ((RemoveBrokerOptions.RemoveBrokerOptionsBuilder) rebalanceOptionsBuilder).build(), userTaskID);
break;
default:
future = apiClient.rebalance(host, CruiseControl.REST_API_PORT, ((RebalanceOptions.RebalanceOptionsBuilder) rebalanceOptionsBuilder).build(), userTaskID);
break;
}
return future.map(response -> handleRebalanceResponse(reconciliation, kafkaRebalance, dryrun, response));
}
use of io.strimzi.operator.common.Reconciliation in project strimzi by strimzi.
the class KafkaRebalanceAssemblyOperator method onRebalancing.
/**
* This method handles the transition from {@code Rebalancing} state.
* It starts a periodic timer in order to check the status of the ongoing rebalance processing on Cruise Control side.
* In order to do that, it calls the related Cruise Control REST API about asking the user task status.
* When the rebalance is finished, the next state is {@code Ready}.
* If the user sets the strimzi.io/rebalance annotation to 'stop', it calls the Cruise Control REST API for stopping the ongoing task
* and then transitions to the {@code Stopped} state.
* If the user sets any other values for the strimzi.io/rebalance annotation, it is just ignored and the user task checks continue.
* This method holds the lock until the rebalance is finished, the ongoing task is stopped or any exception is raised.
*
* @param reconciliation Reconciliation information
* @param host Cruise Control service to which sending the REST API requests
* @param apiClient Cruise Control REST API client instance
* @param kafkaRebalance Current {@code KafkaRebalance} resource
* @param rebalanceAnnotation The current value for the strimzi.io/rebalance annotation
* @return a Future with the next {@code MapAndStatus<ConfigMap, KafkaRebalanceStatus>} including the state
*/
private Future<MapAndStatus<ConfigMap, KafkaRebalanceStatus>> onRebalancing(Reconciliation reconciliation, String host, CruiseControlApi apiClient, KafkaRebalance kafkaRebalance, KafkaRebalanceAnnotation rebalanceAnnotation) {
Promise<MapAndStatus<ConfigMap, KafkaRebalanceStatus>> p = Promise.promise();
if (rebalanceAnnotation == KafkaRebalanceAnnotation.none) {
LOGGER.infoCr(reconciliation, "Starting Cruise Control rebalance user task status timer");
String sessionId = kafkaRebalance.getStatus().getSessionId();
AtomicInteger ccApiErrorCount = new AtomicInteger();
vertx.setPeriodic(REBALANCE_POLLING_TIMER_MS, t -> {
// Check that we have not already failed to contact the API beyond the allowed number of times.
if (ccApiErrorCount.get() >= MAX_API_RETRIES) {
vertx.cancelTimer(t);
p.fail(new CruiseControlRestException("Unable to reach Cruise Control API after " + MAX_API_RETRIES + " attempts"));
}
kafkaRebalanceOperator.getAsync(kafkaRebalance.getMetadata().getNamespace(), kafkaRebalance.getMetadata().getName()).onSuccess(currentKafkaRebalance -> {
// Checking that the resource was not deleted between periodic polls
if (currentKafkaRebalance != null) {
// Safety check as timer might be called again (from a delayed timer firing)
if (state(currentKafkaRebalance) == KafkaRebalanceState.Rebalancing) {
if (rebalanceAnnotation(reconciliation, currentKafkaRebalance) == KafkaRebalanceAnnotation.stop) {
LOGGER.debugCr(reconciliation, "Stopping current Cruise Control rebalance user task");
vertx.cancelTimer(t);
apiClient.stopExecution(host, CruiseControl.REST_API_PORT).onSuccess(r -> p.complete(buildRebalanceStatus(null, KafkaRebalanceState.Stopped, validate(reconciliation, kafkaRebalance)))).onFailure(e -> {
LOGGER.errorCr(reconciliation, "Cruise Control stopping execution failed", e.getCause());
p.fail(e.getCause());
});
} else {
LOGGER.infoCr(reconciliation, "Getting Cruise Control rebalance user task status");
Set<Condition> conditions = validate(reconciliation, kafkaRebalance);
validateAnnotation(reconciliation, conditions, KafkaRebalanceState.Rebalancing, rebalanceAnnotation(reconciliation, currentKafkaRebalance), kafkaRebalance);
apiClient.getUserTaskStatus(host, CruiseControl.REST_API_PORT, sessionId).onSuccess(cruiseControlResponse -> {
JsonObject taskStatusJson = cruiseControlResponse.getJson();
CruiseControlUserTaskStatus taskStatus = CruiseControlUserTaskStatus.lookup(taskStatusJson.getString("Status"));
switch(taskStatus) {
case COMPLETED:
vertx.cancelTimer(t);
LOGGER.infoCr(reconciliation, "Rebalance ({}) is now complete", sessionId);
p.complete(buildRebalanceStatus(kafkaRebalance, null, KafkaRebalanceState.Ready, taskStatusJson, conditions));
break;
case COMPLETED_WITH_ERROR:
// TODO: There doesn't seem to be a way to retrieve the actual error message from the user tasks endpoint?
// We may need to propose an upstream PR for this.
// TODO: Once we can get the error details we need to add an error field to the Rebalance Status to hold
// details of any issues while rebalancing.
LOGGER.errorCr(reconciliation, "Rebalance ({}) optimization proposal has failed to complete", sessionId);
vertx.cancelTimer(t);
p.complete(buildRebalanceStatus(sessionId, KafkaRebalanceState.NotReady, conditions));
break;
case // Rebalance is still in progress
IN_EXECUTION:
// the proposal is complete but the optimisation proposal summary will be missing.
if (currentKafkaRebalance.getStatus().getOptimizationResult() == null || currentKafkaRebalance.getStatus().getOptimizationResult().isEmpty()) {
LOGGER.infoCr(reconciliation, "Rebalance ({}) optimization proposal is now ready and has been added to the status", sessionId);
// Cancel the timer so that the status is returned and updated.
vertx.cancelTimer(t);
p.complete(buildRebalanceStatus(kafkaRebalance, sessionId, KafkaRebalanceState.Rebalancing, taskStatusJson, conditions));
}
ccApiErrorCount.set(0);
// We can then update the status at this point.
break;
case // Rebalance proposal is still being calculated
ACTIVE:
// If a rebalance(dryrun=false) was called and the proposal is still being prepared then the task
// will be in an ACTIVE state. When the proposal is ready it will shift to IN_EXECUTION and we will
// check that the optimisation proposal is added to the status on the next reconcile.
LOGGER.infoCr(reconciliation, "Rebalance ({}) optimization proposal is still being prepared", sessionId);
ccApiErrorCount.set(0);
break;
default:
LOGGER.errorCr(reconciliation, "Unexpected state {}", taskStatus);
vertx.cancelTimer(t);
p.fail("Unexpected state " + taskStatus);
break;
}
}).onFailure(e -> {
LOGGER.errorCr(reconciliation, "Cruise Control getting rebalance task status failed", e.getCause());
// To make sure this error is not just a temporary problem with the network we retry several times.
// If the number of errors pass the MAX_API_ERRORS limit then the period method will fail the promise.
ccApiErrorCount.getAndIncrement();
});
}
} else {
p.complete(new MapAndStatus<>(null, currentKafkaRebalance.getStatus()));
}
} else {
LOGGER.debugCr(reconciliation, "Rebalance resource was deleted, stopping the request time");
vertx.cancelTimer(t);
p.complete();
}
}).onFailure(e -> {
LOGGER.errorCr(reconciliation, "Cruise Control getting rebalance resource failed", e.getCause());
vertx.cancelTimer(t);
p.fail(e.getCause());
});
});
} else {
p.complete(new MapAndStatus<>(null, kafkaRebalance.getStatus()));
}
return p.future();
}
Aggregations