|
13 | 13 | import io.strimzi.api.kafka.model.kafka.listener.GenericKafkaListenerBuilder;
|
14 | 14 | import io.strimzi.api.kafka.model.kafka.listener.KafkaListenerType;
|
15 | 15 | import io.strimzi.api.kafka.model.mirrormaker.KafkaMirrorMakerResources;
|
| 16 | +import io.strimzi.api.kafka.model.mirrormaker2.KafkaMirrorMaker2; |
| 17 | +import io.strimzi.api.kafka.model.mirrormaker2.KafkaMirrorMaker2ClusterSpec; |
| 18 | +import io.strimzi.api.kafka.model.mirrormaker2.KafkaMirrorMaker2ClusterSpecBuilder; |
16 | 19 | import io.strimzi.operator.common.model.Labels;
|
17 | 20 | import io.strimzi.systemtest.Environment;
|
18 | 21 | import io.strimzi.systemtest.TestConstants;
|
|
31 | 34 | import io.strimzi.systemtest.storage.TestStorage;
|
32 | 35 | import io.strimzi.systemtest.templates.crd.KafkaBridgeTemplates;
|
33 | 36 | import io.strimzi.systemtest.templates.crd.KafkaConnectTemplates;
|
| 37 | +import io.strimzi.systemtest.templates.crd.KafkaMirrorMaker2Templates; |
34 | 38 | import io.strimzi.systemtest.templates.crd.KafkaMirrorMakerTemplates;
|
35 | 39 | import io.strimzi.systemtest.templates.crd.KafkaNodePoolTemplates;
|
36 | 40 | import io.strimzi.systemtest.templates.crd.KafkaTemplates;
|
|
41 | 45 | import io.strimzi.systemtest.utils.kafkaUtils.KafkaConnectUtils;
|
42 | 46 | import io.strimzi.systemtest.utils.kafkaUtils.KafkaConnectorUtils;
|
43 | 47 | import io.strimzi.systemtest.utils.kafkaUtils.KafkaUserUtils;
|
| 48 | +import io.strimzi.systemtest.utils.kubeUtils.controllers.JobUtils; |
| 49 | +import io.strimzi.test.TestUtils; |
| 50 | +import io.strimzi.test.WaitException; |
| 51 | +import io.strimzi.test.k8s.KubeClusterResource; |
44 | 52 | import io.vertx.core.cli.annotations.Description;
|
45 | 53 | import org.apache.kafka.clients.consumer.ConsumerConfig;
|
46 | 54 | import org.apache.kafka.clients.producer.ProducerConfig;
|
| 55 | +import org.apache.logging.log4j.Level; |
47 | 56 | import org.apache.logging.log4j.LogManager;
|
48 | 57 | import org.apache.logging.log4j.Logger;
|
49 | 58 | import org.junit.jupiter.api.BeforeAll;
|
50 | 59 | import org.junit.jupiter.api.Tag;
|
51 | 60 |
|
| 61 | +import java.time.Duration; |
| 62 | + |
52 | 63 | import static io.strimzi.systemtest.TestConstants.ACCEPTANCE;
|
53 | 64 | import static io.strimzi.systemtest.TestConstants.ARM64_UNSUPPORTED;
|
54 | 65 | import static io.strimzi.systemtest.TestConstants.BRIDGE;
|
55 | 66 | import static io.strimzi.systemtest.TestConstants.CONNECT;
|
56 | 67 | import static io.strimzi.systemtest.TestConstants.CONNECT_COMPONENTS;
|
57 | 68 | import static io.strimzi.systemtest.TestConstants.HTTP_BRIDGE_DEFAULT_PORT;
|
58 | 69 | import static io.strimzi.systemtest.TestConstants.MIRROR_MAKER;
|
| 70 | +import static io.strimzi.systemtest.TestConstants.MIRROR_MAKER2; |
59 | 71 | import static io.strimzi.systemtest.TestConstants.NODEPORT_SUPPORTED;
|
60 | 72 | import static io.strimzi.systemtest.TestConstants.OAUTH;
|
61 | 73 | import static io.strimzi.systemtest.TestConstants.REGRESSION;
|
@@ -412,6 +424,187 @@ void testMirrorMaker() {
|
412 | 424 | ClientUtils.waitForClientSuccess(consumerName, Environment.TEST_SUITE_NAMESPACE, testStorage.getMessageCount());
|
413 | 425 | }
|
414 | 426 |
|
| 427 | + @Description("As a OAuth MirrorMaker 2, I am able to replicate Topic data using using encrypted communication") |
| 428 | + @IsolatedTest("Using more tha one Kafka cluster in one Namespace") |
| 429 | + @Tag(MIRROR_MAKER2) |
| 430 | + @Tag(NODEPORT_SUPPORTED) |
| 431 | + @SuppressWarnings({"checkstyle:MethodLength"}) |
| 432 | + void testMirrorMaker2() { |
| 433 | + // Nodeport needs cluster wide rights to work properly which is not possible with STRIMZI_RBAC_SCOPE=NAMESPACE |
| 434 | + assumeFalse(Environment.isNamespaceRbacScope()); |
| 435 | + final TestStorage testStorage = new TestStorage(ResourceManager.getTestContext()); |
| 436 | + |
| 437 | + String producerName = OAUTH_PRODUCER_NAME + "-" + testStorage.getClusterName(); |
| 438 | + String consumerName = OAUTH_CONSUMER_NAME + "-" + testStorage.getClusterName(); |
| 439 | + |
| 440 | + resourceManager.createResourceWithWait(KafkaTopicTemplates.topic(oauthClusterName, testStorage.getTopicName(), Environment.TEST_SUITE_NAMESPACE).build()); |
| 441 | + |
| 442 | + KafkaOauthClients oauthExampleClients = new KafkaOauthClientsBuilder() |
| 443 | + .withNamespaceName(Environment.TEST_SUITE_NAMESPACE) |
| 444 | + .withProducerName(producerName) |
| 445 | + .withConsumerName(consumerName) |
| 446 | + .withBootstrapAddress(KafkaResources.tlsBootstrapAddress(oauthClusterName)) |
| 447 | + .withTopicName(testStorage.getTopicName()) |
| 448 | + .withMessageCount(testStorage.getMessageCount()) |
| 449 | + .withOauthClientId(OAUTH_CLIENT_NAME) |
| 450 | + .withOauthClientSecret(OAUTH_CLIENT_SECRET) |
| 451 | + .withOauthTokenEndpointUri(keycloakInstance.getOauthTokenEndpointUri()) |
| 452 | + .build(); |
| 453 | + |
| 454 | + resourceManager.createResourceWithWait(oauthExampleClients.producerStrimziOauthTls(oauthClusterName)); |
| 455 | + ClientUtils.waitForClientSuccess(producerName, Environment.TEST_SUITE_NAMESPACE, testStorage.getMessageCount()); |
| 456 | + |
| 457 | + resourceManager.createResourceWithWait(oauthExampleClients.consumerStrimziOauthTls(oauthClusterName)); |
| 458 | + ClientUtils.waitForClientSuccess(consumerName, Environment.TEST_SUITE_NAMESPACE, testStorage.getMessageCount()); |
| 459 | + |
| 460 | + String targetKafkaCluster = oauthClusterName + "-target"; |
| 461 | + String kafkaSourceClusterName = oauthClusterName; |
| 462 | + |
| 463 | + resourceManager.createResourceWithWait( |
| 464 | + NodePoolsConverter.convertNodePoolsIfNeeded( |
| 465 | + KafkaNodePoolTemplates.brokerPool(testStorage.getNamespaceName(), testStorage.getTargetBrokerPoolName(), targetKafkaCluster, 1).build(), |
| 466 | + KafkaNodePoolTemplates.controllerPool(testStorage.getNamespaceName(), testStorage.getTargetControllerPoolName(), targetKafkaCluster, 1).build() |
| 467 | + ) |
| 468 | + ); |
| 469 | + resourceManager.createResourceWithWait(KafkaTemplates.kafkaEphemeral(targetKafkaCluster, 1, 1) |
| 470 | + .editMetadata() |
| 471 | + .withNamespace(Environment.TEST_SUITE_NAMESPACE) |
| 472 | + .endMetadata() |
| 473 | + .editSpec() |
| 474 | + .editKafka() |
| 475 | + .withListeners(OauthAbstractST.BUILD_OAUTH_TLS_LISTENER.apply(keycloakInstance), |
| 476 | + new GenericKafkaListenerBuilder() |
| 477 | + .withName(TestConstants.EXTERNAL_LISTENER_DEFAULT_NAME) |
| 478 | + .withPort(9094) |
| 479 | + .withType(KafkaListenerType.NODEPORT) |
| 480 | + .withTls(true) |
| 481 | + .withNewKafkaListenerAuthenticationOAuth() |
| 482 | + .withValidIssuerUri(keycloakInstance.getValidIssuerUri()) |
| 483 | + .withJwksExpirySeconds(keycloakInstance.getJwksExpireSeconds()) |
| 484 | + .withJwksRefreshSeconds(keycloakInstance.getJwksRefreshSeconds()) |
| 485 | + .withJwksEndpointUri(keycloakInstance.getJwksEndpointUri()) |
| 486 | + .withUserNameClaim(keycloakInstance.getUserNameClaim()) |
| 487 | + .withTlsTrustedCertificates( |
| 488 | + new CertSecretSourceBuilder() |
| 489 | + .withSecretName(KeycloakInstance.KEYCLOAK_SECRET_NAME) |
| 490 | + .withCertificate(KeycloakInstance.KEYCLOAK_SECRET_CERT) |
| 491 | + .build()) |
| 492 | + .withDisableTlsHostnameVerification(true) |
| 493 | + .endKafkaListenerAuthenticationOAuth() |
| 494 | + .build()) |
| 495 | + .endKafka() |
| 496 | + .endSpec() |
| 497 | + .build()); |
| 498 | + |
| 499 | + // Deploy MirrorMaker2 with OAuth |
| 500 | + KafkaMirrorMaker2ClusterSpec sourceClusterWithOauth = new KafkaMirrorMaker2ClusterSpecBuilder() |
| 501 | + .withAlias(kafkaSourceClusterName) |
| 502 | + .withConfig(connectorConfig) |
| 503 | + .withBootstrapServers(KafkaResources.tlsBootstrapAddress(oauthClusterName)) |
| 504 | + // this is for kafka tls connection |
| 505 | + .withNewTls() |
| 506 | + .withTrustedCertificates(new CertSecretSourceBuilder() |
| 507 | + .withCertificate("ca.crt") |
| 508 | + .withSecretName(KafkaResources.clusterCaCertificateSecretName(oauthClusterName)) |
| 509 | + .build()) |
| 510 | + .endTls() |
| 511 | + .withNewKafkaClientAuthenticationOAuth() |
| 512 | + .withTokenEndpointUri(keycloakInstance.getOauthTokenEndpointUri()) |
| 513 | + .withClientId("kafka-mirror-maker-2") |
| 514 | + .withNewClientSecret() |
| 515 | + .withSecretName(MIRROR_MAKER_2_OAUTH_SECRET) |
| 516 | + .withKey(OAUTH_KEY) |
| 517 | + .endClientSecret() |
| 518 | + .withConnectTimeoutSeconds(CONNECT_TIMEOUT_S) |
| 519 | + .withReadTimeoutSeconds(READ_TIMEOUT_S) |
| 520 | + // this is for authorization server tls connection |
| 521 | + .withTlsTrustedCertificates(new CertSecretSourceBuilder() |
| 522 | + .withSecretName(KeycloakInstance.KEYCLOAK_SECRET_NAME) |
| 523 | + .withCertificate(KeycloakInstance.KEYCLOAK_SECRET_CERT) |
| 524 | + .build()) |
| 525 | + .withDisableTlsHostnameVerification(true) |
| 526 | + .endKafkaClientAuthenticationOAuth() |
| 527 | + .build(); |
| 528 | + |
| 529 | + KafkaMirrorMaker2ClusterSpec targetClusterWithOauth = new KafkaMirrorMaker2ClusterSpecBuilder() |
| 530 | + .withAlias(testStorage.getTargetClusterName()) |
| 531 | + .withConfig(connectorConfig) |
| 532 | + .withBootstrapServers(KafkaResources.tlsBootstrapAddress(targetKafkaCluster)) |
| 533 | + // this is for kafka tls connection |
| 534 | + .withNewTls() |
| 535 | + .withTrustedCertificates(new CertSecretSourceBuilder() |
| 536 | + .withCertificate("ca.crt") |
| 537 | + .withSecretName(KafkaResources.clusterCaCertificateSecretName(targetKafkaCluster)) |
| 538 | + .build()) |
| 539 | + .endTls() |
| 540 | + .withNewKafkaClientAuthenticationOAuth() |
| 541 | + .withTokenEndpointUri(keycloakInstance.getOauthTokenEndpointUri()) |
| 542 | + .withClientId("kafka-mirror-maker-2") |
| 543 | + .withNewClientSecret() |
| 544 | + .withSecretName(MIRROR_MAKER_2_OAUTH_SECRET) |
| 545 | + .withKey(OAUTH_KEY) |
| 546 | + .endClientSecret() |
| 547 | + .withConnectTimeoutSeconds(CONNECT_TIMEOUT_S) |
| 548 | + .withReadTimeoutSeconds(READ_TIMEOUT_S) |
| 549 | + // this is for authorization server tls connection |
| 550 | + .withTlsTrustedCertificates(new CertSecretSourceBuilder() |
| 551 | + .withSecretName(KeycloakInstance.KEYCLOAK_SECRET_NAME) |
| 552 | + .withCertificate(KeycloakInstance.KEYCLOAK_SECRET_CERT) |
| 553 | + .build()) |
| 554 | + .withDisableTlsHostnameVerification(true) |
| 555 | + .endKafkaClientAuthenticationOAuth() |
| 556 | + .build(); |
| 557 | + |
| 558 | + String kafkaTargetClusterTopicName = kafkaSourceClusterName + "." + testStorage.getTopicName(); |
| 559 | + |
| 560 | + resourceManager.createResourceWithWait(KafkaMirrorMaker2Templates.kafkaMirrorMaker2(oauthClusterName, testStorage.getTargetClusterName(), kafkaSourceClusterName, 1, false) |
| 561 | + .editMetadata() |
| 562 | + .withNamespace(Environment.TEST_SUITE_NAMESPACE) |
| 563 | + .endMetadata() |
| 564 | + .editSpec() |
| 565 | + .withClusters(sourceClusterWithOauth, targetClusterWithOauth) |
| 566 | + .editFirstMirror() |
| 567 | + .withSourceCluster(kafkaSourceClusterName) |
| 568 | + .endMirror() |
| 569 | + .endSpec() |
| 570 | + .build()); |
| 571 | + |
| 572 | + final String kafkaMirrorMaker2PodName = kubeClient().listPods(Environment.TEST_SUITE_NAMESPACE, oauthClusterName, Labels.STRIMZI_KIND_LABEL, KafkaMirrorMaker2.RESOURCE_KIND).get(0).getMetadata().getName(); |
| 573 | + final String kafkaMirrorMaker2Logs = KubeClusterResource.cmdKubeClient(Environment.TEST_SUITE_NAMESPACE).execInCurrentNamespace(Level.DEBUG, "logs", kafkaMirrorMaker2PodName).out(); |
| 574 | + verifyOauthConfiguration(kafkaMirrorMaker2Logs); |
| 575 | + |
| 576 | + TestUtils.waitFor("MirrorMaker2 to copy messages from " + kafkaSourceClusterName + " to " + testStorage.getTargetClusterName(), |
| 577 | + Duration.ofSeconds(30).toMillis(), TestConstants.TIMEOUT_FOR_MIRROR_MAKER_COPY_MESSAGES_BETWEEN_BROKERS, |
| 578 | + () -> { |
| 579 | + LOGGER.info("Deleting Job: {}/{}", Environment.TEST_SUITE_NAMESPACE, consumerName); |
| 580 | + JobUtils.deleteJobWithWait(Environment.TEST_SUITE_NAMESPACE, consumerName); |
| 581 | + |
| 582 | + LOGGER.info("Creating new client with new consumer-group and also to point on {} cluster", testStorage.getTargetClusterName()); |
| 583 | + |
| 584 | + KafkaOauthClients kafkaOauthClientJob = new KafkaOauthClientsBuilder() |
| 585 | + .withNamespaceName(Environment.TEST_SUITE_NAMESPACE) |
| 586 | + .withProducerName(producerName) |
| 587 | + .withConsumerName(consumerName) |
| 588 | + .withBootstrapAddress(KafkaResources.tlsBootstrapAddress(targetKafkaCluster)) |
| 589 | + .withTopicName(kafkaTargetClusterTopicName) |
| 590 | + .withMessageCount(testStorage.getMessageCount()) |
| 591 | + .withOauthClientId(OAUTH_CLIENT_NAME) |
| 592 | + .withOauthClientSecret(OAUTH_CLIENT_SECRET) |
| 593 | + .withOauthTokenEndpointUri(keycloakInstance.getOauthTokenEndpointUri()) |
| 594 | + .build(); |
| 595 | + |
| 596 | + resourceManager.createResourceWithWait(kafkaOauthClientJob.consumerStrimziOauthTls(targetKafkaCluster)); |
| 597 | + |
| 598 | + try { |
| 599 | + ClientUtils.waitForClientSuccess(consumerName, Environment.TEST_SUITE_NAMESPACE, testStorage.getMessageCount()); |
| 600 | + return true; |
| 601 | + } catch (WaitException e) { |
| 602 | + LOGGER.error("Failed while waiting for consumer to succeed", e); |
| 603 | + return false; |
| 604 | + } |
| 605 | + }); |
| 606 | + } |
| 607 | + |
415 | 608 | @ParallelTest
|
416 | 609 | void testIntrospectionEndpoint() {
|
417 | 610 | final TestStorage testStorage = new TestStorage(ResourceManager.getTestContext());
|
|
0 commit comments