use of io.fabric8.kubernetes.api.model.Condition in project strimzi by strimzi.
the class KafkaRebalanceAssemblyOperatorTest method testNewWithMissingHardGoalsAndRefresh.
/**
* Tests the transition from 'New' to 'NotReady' due to "missing hard goals" error
* The KafkaRebalance resource is updated with "skip hard goals check" and refreshed; it then moves to the 'ProposalReady' state
*
* 1. A new KafkaRebalance resource is created with some specified not hard goals; it is in the New state
* 2. The operator requests a rebalance proposal through the Cruise Control REST API
* 3. The operator receives a "missing hard goals" error instead of a proposal
* 4. The KafkaRebalance resource moves to the 'NotReady' state
* 5. The rebalance is updated with the 'skip hard goals check' field to "true" and annotated with 'strimzi.io/rebalance=refresh'
* 6. The operator requests a rebalance proposal through the Cruise Control REST API
* 7. The rebalance proposal is ready on the first call
* 8. The KafkaRebalance resource moves to the 'ProposalReady' state
*/
@Test
public void testNewWithMissingHardGoalsAndRefresh(VertxTestContext context) throws IOException, URISyntaxException {
// Setup the rebalance endpoint to get error about hard goals
MockCruiseControl.setupCCRebalanceBadGoalsError(ccServer);
KafkaRebalanceSpec kafkaRebalanceSpec = new KafkaRebalanceSpecBuilder().withGoals("DiskCapacityGoal", "CpuCapacityGoal").build();
KafkaRebalance kr = createKafkaRebalance(CLUSTER_NAMESPACE, CLUSTER_NAME, RESOURCE_NAME, kafkaRebalanceSpec);
Crds.kafkaRebalanceOperation(kubernetesClient).inNamespace(CLUSTER_NAMESPACE).create(kr);
// the Kafka cluster isn't deployed in the namespace
when(mockKafkaOps.getAsync(CLUSTER_NAMESPACE, CLUSTER_NAME)).thenReturn(Future.succeededFuture(kafka));
mockSecretResources();
mockRebalanceOperator(mockRebalanceOps, mockCmOps, CLUSTER_NAMESPACE, RESOURCE_NAME, kubernetesClient);
Checkpoint checkpoint = context.checkpoint();
kcrao.reconcileRebalance(new Reconciliation("test-trigger", KafkaRebalance.RESOURCE_KIND, CLUSTER_NAMESPACE, RESOURCE_NAME), kr).onComplete(context.succeeding(v -> context.verify(() -> {
// the resource moved from New to NotReady due to the error
KafkaRebalance kr1 = Crds.kafkaRebalanceOperation(kubernetesClient).inNamespace(CLUSTER_NAMESPACE).withName(RESOURCE_NAME).get();
assertThat(kr1, StateMatchers.hasState());
Condition condition = kcrao.rebalanceStateCondition(kr1.getStatus());
assertThat(condition, StateMatchers.hasStateInCondition(KafkaRebalanceState.NotReady, CruiseControlRestException.class, "Error processing POST request '/rebalance' due to: " + "'java.lang.IllegalArgumentException: Missing hard goals [NetworkInboundCapacityGoal, DiskCapacityGoal, RackAwareGoal, NetworkOutboundCapacityGoal, CpuCapacityGoal, ReplicaCapacityGoal] " + "in the provided goals: [RackAwareGoal, ReplicaCapacityGoal]. " + "Add skip_hard_goal_check=true parameter to ignore this sanity check.'."));
}))).compose(v -> {
ccServer.reset();
try {
// Setup the rebalance endpoint with the number of pending calls before a response is received.
MockCruiseControl.setupCCRebalanceResponse(ccServer, 0);
} catch (IOException | URISyntaxException e) {
context.failNow(e);
}
// set the skip hard goals check flag
KafkaRebalance kr2 = Crds.kafkaRebalanceOperation(kubernetesClient).inNamespace(CLUSTER_NAMESPACE).withName(RESOURCE_NAME).get();
KafkaRebalance patchedKr = new KafkaRebalanceBuilder(kr2).editSpec().withSkipHardGoalCheck(true).endSpec().build();
Crds.kafkaRebalanceOperation(kubernetesClient).inNamespace(CLUSTER_NAMESPACE).withName(RESOURCE_NAME).patch(patchedKr);
// apply the "refresh" annotation to the resource in the NotReady state
patchedKr = annotate(kubernetesClient, CLUSTER_NAMESPACE, RESOURCE_NAME, KafkaRebalanceAnnotation.refresh);
// trigger another reconcile to process the NotReady state
return kcrao.reconcileRebalance(new Reconciliation("test-trigger", KafkaRebalance.RESOURCE_KIND, CLUSTER_NAMESPACE, RESOURCE_NAME), patchedKr);
}).onComplete(context.succeeding(v -> {
// the resource transitioned from 'NotReady' to 'ProposalReady'
assertState(context, kubernetesClient, CLUSTER_NAMESPACE, RESOURCE_NAME, KafkaRebalanceState.ProposalReady);
checkpoint.flag();
}));
}
use of io.fabric8.kubernetes.api.model.Condition 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.fabric8.kubernetes.api.model.Condition in project strimzi by strimzi.
the class OperatorMetricsTest method testDeleteCountsReconcile.
@Test
public void testDeleteCountsReconcile(VertxTestContext context) {
MetricsProvider metrics = createCleanMetricsProvider();
AbstractWatchableStatusedResourceOperator resourceOperator = new AbstractWatchableStatusedResourceOperator(vertx, null, "TestResource") {
@Override
protected MixedOperation operation() {
return null;
}
@Override
public HasMetadata get(String namespace, String name) {
return null;
}
@Override
public Future updateStatusAsync(Reconciliation reconciliation, HasMetadata resource) {
return null;
}
};
AbstractOperator operator = new AbstractOperator(vertx, "TestResource", resourceOperator, metrics, null) {
@Override
protected Future createOrUpdate(Reconciliation reconciliation, CustomResource resource) {
return null;
}
@Override
public Set<Condition> validate(Reconciliation reconciliation, CustomResource resource) {
// Do nothing
return emptySet();
}
@Override
protected Future<Boolean> delete(Reconciliation reconciliation) {
return Future.succeededFuture(Boolean.TRUE);
}
@Override
protected Status createStatus() {
return new Status() {
};
}
};
Checkpoint async = context.checkpoint();
operator.reconcile(new Reconciliation("test", "TestResource", "my-namespace", "my-resource")).onComplete(context.succeeding(v -> context.verify(() -> {
MeterRegistry registry = metrics.meterRegistry();
Tag selectorTag = Tag.of("selector", "");
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations").tag("kind", "TestResource").counter().count(), is(1.0));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.successful").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.successful").tag("kind", "TestResource").counter().count(), is(1.0));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.duration").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.duration").tag("kind", "TestResource").timer().count(), is(1L));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.duration").tag("kind", "TestResource").timer().totalTime(TimeUnit.MILLISECONDS), greaterThan(0.0));
assertThrows(MeterNotFoundException.class, () -> {
registry.get(AbstractOperator.METRICS_PREFIX + "resource.state").tag("kind", "TestResource").tag("name", "my-resource").tag("resource-namespace", "my-namespace").gauge();
});
async.flag();
})));
}
use of io.fabric8.kubernetes.api.model.Condition in project strimzi by strimzi.
the class OperatorMetricsTest method testPauseReconcile.
@Test
public void testPauseReconcile(VertxTestContext context) {
MetricsProvider metrics = createCleanMetricsProvider();
AbstractWatchableStatusedResourceOperator resourceOperator = resourceOperatorWithExistingPausedResource();
AbstractOperator operator = new AbstractOperator(vertx, "TestResource", resourceOperator, metrics, null) {
@Override
protected Future createOrUpdate(Reconciliation reconciliation, CustomResource resource) {
return Future.succeededFuture();
}
@Override
public Set<Condition> validate(Reconciliation reconciliation, CustomResource resource) {
return new HashSet<>();
}
@Override
protected Future<Boolean> delete(Reconciliation reconciliation) {
return null;
}
@Override
protected Status createStatus() {
return new Status() {
};
}
};
Checkpoint async = context.checkpoint();
operator.reconcile(new Reconciliation("test", "TestResource", "my-namespace", "my-resource")).onComplete(context.succeeding(v -> context.verify(() -> {
MeterRegistry registry = metrics.meterRegistry();
Tag selectorTag = Tag.of("selector", "");
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations").tag("kind", "TestResource").counter().count(), is(1.0));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.successful").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.successful").tag("kind", "TestResource").counter().count(), is(1.0));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "resources.paused").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "resources.paused").tag("kind", "TestResource").gauge().value(), is(1.0));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.duration").meter().getId().getTags().get(2), is(selectorTag));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.duration").tag("kind", "TestResource").timer().count(), is(1L));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "reconciliations.duration").tag("kind", "TestResource").timer().totalTime(TimeUnit.MILLISECONDS), greaterThan(0.0));
assertThat(registry.get(AbstractOperator.METRICS_PREFIX + "resource.state").tag("kind", "TestResource").tag("name", "my-resource").tag("resource-namespace", "my-namespace").gauge().value(), is(1.0));
async.flag();
})));
}
use of io.fabric8.kubernetes.api.model.Condition in project strimzi by strimzi.
the class ValidationVisitorTest method testValidationErrorsAreLogged.
@Test
public void testValidationErrorsAreLogged() {
Kafka k = TestUtils.fromYaml("/example.yaml", Kafka.class, true);
assertThat(k, is(notNullValue()));
TestLogger logger = TestLogger.create(ValidationVisitorTest.class);
HasMetadata resource = new KafkaBuilder().withNewMetadata().withName("testname").withNamespace("testnamespace").endMetadata().withApiVersion("v1beta2").build();
Set<Condition> warningConditions = new HashSet<>();
ResourceVisitor.visit(Reconciliation.DUMMY_RECONCILIATION, k, new ValidationVisitor(resource, logger, warningConditions));
List<String> warningMessages = warningConditions.stream().map(Condition::getMessage).collect(Collectors.toList());
assertThat(warningMessages, hasItem("Contains object at path spec.kafka with an unknown property: foo"));
assertThat(warningMessages, hasItem("In API version v1beta2 the enableECDSA property at path spec.kafka.listeners.auth.enableECDSA has been deprecated."));
assertThat(warningMessages, hasItem("In API version v1beta2 the service property at path spec.kafkaExporter.template.service has been deprecated. " + "The Kafka Exporter service has been removed."));
logger.assertLoggedAtLeastOnce(lm -> lm.level() == Level.WARN && lm.formattedMessage().matches("Reconciliation #[0-9]*\\(test\\) kind\\(namespace\\/name\\): " + "Contains object at path spec.kafka with an unknown property: foo"));
logger.assertLoggedAtLeastOnce(lm -> lm.level() == Level.WARN && lm.formattedMessage().matches("Reconciliation #[0-9]*\\(test\\) kind\\(namespace\\/name\\): " + "In API version v1beta2 the enableECDSA property at path spec.kafka.listeners.auth.enableECDSA has been deprecated."));
logger.assertLoggedAtLeastOnce(lm -> lm.level() == Level.WARN && lm.formattedMessage().matches("Reconciliation #[0-9]*\\(test\\) kind\\(namespace\\/name\\): " + "In API version v1beta2 the service property at path spec.kafkaExporter.template.service has been deprecated. " + "The Kafka Exporter service has been removed."));
}
Aggregations