diff --git a/benchmarks/src/test/java/zipkin2/server/ServerIntegratedBenchmark.java b/benchmarks/src/test/java/zipkin2/server/ServerIntegratedBenchmark.java index 3fa78861278..47df0d10e4a 100644 --- a/benchmarks/src/test/java/zipkin2/server/ServerIntegratedBenchmark.java +++ b/benchmarks/src/test/java/zipkin2/server/ServerIntegratedBenchmark.java @@ -91,7 +91,7 @@ class ServerIntegratedBenchmark { @Test void elasticsearch() throws Exception { GenericContainer elasticsearch = - new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-elasticsearch7:3.0.1")) + new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-elasticsearch7:3.0.4")) .withNetwork(Network.SHARED) .withNetworkAliases("elasticsearch") .withLabel("name", "elasticsearch") @@ -105,7 +105,7 @@ class ServerIntegratedBenchmark { @Test void cassandra3() throws Exception { GenericContainer cassandra = - new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-cassandra:3.0.1")) + new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-cassandra:3.0.4")) .withNetwork(Network.SHARED) .withNetworkAliases("cassandra") .withLabel("name", "cassandra") @@ -119,7 +119,7 @@ class ServerIntegratedBenchmark { @Test void mysql() throws Exception { GenericContainer mysql = - new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-mysql:3.0.1")) + new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-mysql:3.0.4")) .withNetwork(Network.SHARED) .withNetworkAliases("mysql") .withLabel("name", "mysql") diff --git a/docker/examples/docker-compose-eureka.yml b/docker/examples/docker-compose-eureka.yml index 15bb4cc417c..ef611144065 100644 --- a/docker/examples/docker-compose-eureka.yml +++ b/docker/examples/docker-compose-eureka.yml @@ -24,9 +24,13 @@ services: eureka: image: ghcr.io/openzipkin/zipkin-eureka:${TAG:-latest} container_name: eureka - # Uncomment to expose the eureka port for testing - # ports: - # - 8761:8761 +# Uncomment to require authentication +# environment: +# - EUREKA_USERNAME=username +# - EUREKA_PASSWORD=password +# Uncomment to expose the eureka port for testing +# ports: +# - 8761:8761 zipkin: extends: @@ -34,6 +38,8 @@ services: service: zipkin environment: - EUREKA_SERVICE_URL=http://eureka:8761/eureka/v2 + # Uncomment to authenticate eureka + #- EUREKA_SERVICE_URL=http://username:password@eureka:8761/eureka/v2 - EUREKA_HOSTNAME=zipkin depends_on: eureka: @@ -46,6 +52,8 @@ services: entrypoint: start-frontend environment: - EUREKA_SERVICE_URL=http://eureka:8761/eureka/v2 + # Uncomment to authenticate eureka + #- EUREKA_SERVICE_URL=http://username:password@eureka:8761/eureka/v2 ports: - 8081:8081 depends_on: @@ -61,6 +69,8 @@ services: entrypoint: start-backend environment: - EUREKA_SERVICE_URL=http://eureka:8761/eureka/v2 + # Uncomment to authenticate eureka + #- EUREKA_SERVICE_URL=http://username:password@eureka:8761/eureka/v2 depends_on: zipkin: condition: service_healthy diff --git a/zipkin-collector/activemq/src/test/java/zipkin2/collector/activemq/ActiveMQExtension.java b/zipkin-collector/activemq/src/test/java/zipkin2/collector/activemq/ActiveMQExtension.java index 4147cf16138..9072fd11edb 100644 --- a/zipkin-collector/activemq/src/test/java/zipkin2/collector/activemq/ActiveMQExtension.java +++ b/zipkin-collector/activemq/src/test/java/zipkin2/collector/activemq/ActiveMQExtension.java @@ -64,7 +64,7 @@ String brokerURL() { // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 static final class ActiveMQContainer extends GenericContainer { ActiveMQContainer() { - super(parse("ghcr.io/openzipkin/zipkin-activemq:3.0.1")); + super(parse("ghcr.io/openzipkin/zipkin-activemq:3.0.4")); withExposedPorts(ACTIVEMQ_PORT); waitStrategy = Wait.forListeningPorts(ACTIVEMQ_PORT); withStartupTimeout(Duration.ofSeconds(60)); diff --git a/zipkin-collector/kafka/src/test/java/zipkin2/collector/kafka/KafkaExtension.java b/zipkin-collector/kafka/src/test/java/zipkin2/collector/kafka/KafkaExtension.java index af9dd786733..d3ac12e89b2 100644 --- a/zipkin-collector/kafka/src/test/java/zipkin2/collector/kafka/KafkaExtension.java +++ b/zipkin-collector/kafka/src/test/java/zipkin2/collector/kafka/KafkaExtension.java @@ -92,7 +92,7 @@ KafkaCollector.Builder newCollectorBuilder(String topic, int streams) { // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 static final class KafkaContainer extends GenericContainer { KafkaContainer() { - super(parse("ghcr.io/openzipkin/zipkin-kafka:3.0.1")); + super(parse("ghcr.io/openzipkin/zipkin-kafka:3.0.4")); waitStrategy = Wait.forHealthcheck(); // 19092 is for connections from the Docker host and needs to be used as a fixed port. // TODO: someone who knows Kafka well, make ^^ comment better! diff --git a/zipkin-collector/rabbitmq/src/test/java/zipkin2/collector/rabbitmq/RabbitMQExtension.java b/zipkin-collector/rabbitmq/src/test/java/zipkin2/collector/rabbitmq/RabbitMQExtension.java index 3a3e5de8fa7..1f1475228e5 100644 --- a/zipkin-collector/rabbitmq/src/test/java/zipkin2/collector/rabbitmq/RabbitMQExtension.java +++ b/zipkin-collector/rabbitmq/src/test/java/zipkin2/collector/rabbitmq/RabbitMQExtension.java @@ -83,7 +83,7 @@ int port() { // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 static final class RabbitMQContainer extends GenericContainer { RabbitMQContainer() { - super(parse("ghcr.io/openzipkin/zipkin-rabbitmq:3.0.1")); + super(parse("ghcr.io/openzipkin/zipkin-rabbitmq:3.0.4")); withExposedPorts(RABBIT_PORT); waitStrategy = Wait.forLogMessage(".*Server startup complete.*", 1); withStartupTimeout(Duration.ofSeconds(60)); diff --git a/zipkin-server/README.md b/zipkin-server/README.md index 7555e4861a2..9d0826a2c11 100644 --- a/zipkin-server/README.md +++ b/zipkin-server/README.md @@ -529,6 +529,11 @@ If you are using a containerized environment, you may need to set `EUREKA_HOSTNA detecting the wrong hostname. For example, if using docker-compose, set `EUREKA_HOSTNAME` to zipkin's `container_name`. +If your Eureka server requires authentication, adjust `EUREKA_SERVICE_URL` accordingly. If user info +is present, those credentials will be used for BASIC authentication. For example, if the URL is +`https://myuser:mypassword@1.1.3.1/eureka/v2/`, requests to Eureka will authenticate with the user +"myuser" and password "mypassword". + Note: Eureka server registration only includes host and port details. Tracers need to resolve this to the POST endpoint "/api/v2/spans". diff --git a/zipkin-server/src/main/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryProperties.java b/zipkin-server/src/main/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryProperties.java index a17ea90e36a..22344b66587 100644 --- a/zipkin-server/src/main/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryProperties.java +++ b/zipkin-server/src/main/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryProperties.java @@ -13,9 +13,12 @@ */ package zipkin2.server.internal.eureka; +import com.linecorp.armeria.common.auth.BasicToken; import com.linecorp.armeria.server.eureka.EurekaUpdatingListener; import com.linecorp.armeria.server.eureka.EurekaUpdatingListenerBuilder; import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -30,8 +33,14 @@ */ @ConfigurationProperties("zipkin.discovery.eureka") class ZipkinEurekaDiscoveryProperties implements Serializable { // for Spark jobs - /** URL of the Eureka v2 endpoint. e.g. http://eureka:8761/eureka/v2 */ - private String serviceUrl; + /** + * URL of the Eureka v2 endpoint. e.g. http://eureka:8761/eureka/v2 + * + *

Note: When present, {@link URI#getUserInfo() userInfo} credentials will be used for BASIC + * authentication. For example, if the URL is "https://myuser:mypassword@1.1.3.1/eureka/v2/", + * requests to Eureka will authenticate with the user "myuser" and password "mypassword". + */ + private URI serviceUrl; /** The appName property of this instance of zipkin. */ private String appName; /** The instanceId property of this instance of zipkin. */ @@ -39,12 +48,36 @@ class ZipkinEurekaDiscoveryProperties implements Serializable { // for Spark job /** The hostName property of this instance of zipkin. */ private String hostname; - public String getServiceUrl() { + private BasicToken auth; + + public URI getServiceUrl() { return serviceUrl; } - public void setServiceUrl(String serviceUrl) { - this.serviceUrl = emptyToNull(serviceUrl); + public void setServiceUrl(URI serviceUrl) { + if (serviceUrl == null || serviceUrl.toString().isEmpty()) { + this.serviceUrl = null; + return; + } + if (serviceUrl.getUserInfo() != null) { + String[] ui = serviceUrl.getUserInfo().split(":"); + if (ui.length == 2) { + auth = BasicToken.ofBasic(ui[0], ui[1]); + } + } + this.serviceUrl = stripBaseUrl(serviceUrl); + } + + // Strip the credentials and any invalid query or fragment from the URI: + // The Eureka API doesn't define any global query params or fragment. + // See https://github.com/Netflix/eureka/wiki/Eureka-REST-operations + static URI stripBaseUrl(URI serviceUrl) { + try { + return new URI(serviceUrl.getScheme(), null, serviceUrl.getHost(), serviceUrl.getPort(), + serviceUrl.getPath(), null, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } } public String getAppName() { @@ -72,7 +105,8 @@ public void setHostname(String hostname) { } EurekaUpdatingListenerBuilder toBuilder() { - final EurekaUpdatingListenerBuilder result = EurekaUpdatingListener.builder(serviceUrl); + EurekaUpdatingListenerBuilder result = EurekaUpdatingListener.builder(serviceUrl); + if (auth != null) result.auth(auth); if (appName != null) result.appName(appName); if (instanceId != null) result.instanceId(instanceId); if (hostname != null) result.hostname(hostname); diff --git a/zipkin-server/src/main/resources/zipkin-server-shared.yml b/zipkin-server/src/main/resources/zipkin-server-shared.yml index 64adbcc345c..b704e13af29 100644 --- a/zipkin-server/src/main/resources/zipkin-server-shared.yml +++ b/zipkin-server/src/main/resources/zipkin-server-shared.yml @@ -283,8 +283,10 @@ logging: com.datastax.oss.driver.internal.core.time.Clock: 'WARN' # Unless it's serious we don't want to know com.linecorp.armeria: 'WARN' - # # But allow to say it's ready to serve requests + # # But log when ready to serve requests com.linecorp.armeria.server.Server: 'INFO' + # # and when registered in Eureka + com.linecorp.armeria.server.eureka.EurekaUpdatingListener: 'INFO' # kafka is quite chatty, so we switch everything off by default org.apache.kafka: 'OFF' # # investigate /api/v2/dependencies diff --git a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/Access.java b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/Access.java deleted file mode 100644 index 04a5b080c7d..00000000000 --- a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/Access.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.server.internal.eureka; - -import com.linecorp.armeria.server.eureka.EurekaUpdatingListenerBuilder; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Configuration; - -/** opens package access for testing */ -public final class Access { - - /** Just registering properties to avoid automatically connecting to a Eureka server */ - public static void registerEurekaProperties(AnnotationConfigApplicationContext context) { - context.register( - PropertyPlaceholderAutoConfiguration.class, EnableEurekaDiscoveryProperties.class); - } - - @Configuration - @EnableConfigurationProperties(ZipkinEurekaDiscoveryProperties.class) - static class EnableEurekaDiscoveryProperties {} - - public static EurekaUpdatingListenerBuilder discoveryBuilder( - AnnotationConfigApplicationContext context) { - return context.getBean(ZipkinEurekaDiscoveryProperties.class).toBuilder(); - } -} diff --git a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/BaseITZipkinEureka.java b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/BaseITZipkinEureka.java new file mode 100644 index 00000000000..216a390aa8a --- /dev/null +++ b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/BaseITZipkinEureka.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.server.internal.eureka; + +import com.jayway.jsonpath.JsonPath; +import com.linecorp.armeria.server.Server; +import java.io.IOException; +import java.time.Duration; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Testcontainers; +import zipkin.server.ZipkinServer; + +import static okhttp3.Credentials.basic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.testcontainers.utility.DockerImageName.parse; + +/** + * Integration test for Eureka, which validates with Eureka REST operations + * + *

Note: We only validate that authentication works, as it is cheaper than also testing without + * it. + */ +@SpringBootTest( + classes = ZipkinServer.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE, // RANDOM_PORT requires spring-web + properties = { + "server.port=0", + "spring.config.name=zipkin-server", + "spring.main.banner-mode=off", + "zipkin.storage.type=", // cheat and test empty storage type + "zipkin.collector.http.enabled=false", + "zipkin.query.enabled=false", + "zipkin.ui.enabled=false" + }) +@Tag("docker") +@Testcontainers(disabledWithoutDocker = true) +@TestMethodOrder(OrderAnnotation.class) // so that deregistration tests don't flake the others. +abstract class BaseITZipkinEureka { + /** + * Path under the Eureka v2 endpoint for the app named "zipkin". + * Note that Eureka always coerces app names to uppercase. + */ + private static final String APPS_ZIPKIN = "apps/ZIPKIN"; + private final OkHttpClient client = new OkHttpClient.Builder().followRedirects(false).build(); + + @Autowired Server zipkin; + + final HttpUrl serviceUrl; + + BaseITZipkinEureka(HttpUrl serviceUrl) { + this.serviceUrl = serviceUrl; + } + + @BeforeEach void awaitRegistration() { + // The zipkin server may start before Eureka processes the registration + await().until(this::getEurekaZipkinAppAsString, (s) -> true); + } + + @Test @Order(1) void registersInEureka() throws IOException { + String json = getEurekaZipkinAppAsString(); + + // Make sure the health status is OK + assertThat(readString(json, "$.application.instance[0].status")) + .isEqualTo("UP"); + + String zipkinHostname = zipkin.defaultHostname(); + int zipkinPort = zipkin.activePort().localAddress().getPort(); + + // Note: Netflix/Eureka says use hostname, which can conflict on laptops. + // Armeria adopts the spring-cloud-netflix convention shown here. + assertThat(readString(json, "$.application.instance[0].instanceId")) + .isEqualTo(zipkinHostname + ":zipkin:" + zipkinPort); + + // Make sure the vip address is relevant + assertThat(readString(json, "$.application.instance[0].vipAddress")) + .isEqualTo(zipkinHostname + ":" + zipkinPort); + } + + @Test @Order(2) void deregistersOnClose() { + zipkin.close(); + await().untilAsserted( // wait for deregistration + () -> { + try (Response response = getEurekaZipkinApp()) { + assertThat(response.code()).isEqualTo(404); + } + }); + } + + private String getEurekaZipkinAppAsString() throws IOException { + try (Response response = getEurekaZipkinApp(); ResponseBody body = response.body()) { + assertThat(response.isSuccessful()).withFailMessage(response.toString()).isTrue(); + return body != null ? body.string() : ""; + } + } + + private Response getEurekaZipkinApp() throws IOException { + HttpUrl url = serviceUrl.newBuilder().addEncodedPathSegments(APPS_ZIPKIN).build(); + Request.Builder rb = new Request.Builder().url(url) + .header("Accept", "application/json"); // XML is default + if (!url.username().isEmpty()) { + rb.header("Authorization", basic(url.username(), url.password())); + } + return client.newCall(rb.build()).execute(); + } + + static final class EurekaContainer extends GenericContainer { + static final Logger LOGGER = LoggerFactory.getLogger(EurekaContainer.class); + static final int EUREKA_PORT = 8761; + + EurekaContainer(Map env) { + super(parse("ghcr.io/openzipkin/zipkin-eureka:3.0.4")); + withEnv(env); + withExposedPorts(EUREKA_PORT); + waitStrategy = Wait.forHealthcheck(); + withStartupTimeout(Duration.ofSeconds(60)); + withLogConsumer(new Slf4jLogConsumer(LOGGER)); + } + + HttpUrl serviceUrl() { + return new HttpUrl.Builder() + .scheme("http") + .host(getHost()).port(getMappedPort(EUREKA_PORT)) + .addEncodedPathSegments("eureka/v2").build(); + } + } + + static String readString(String json, String jsonPath) { + return JsonPath.compile(jsonPath).read(json); + } +} diff --git a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEureka.java b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEureka.java index db57f28630c..505be8a111a 100644 --- a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEureka.java +++ b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEureka.java @@ -13,137 +13,25 @@ */ package zipkin2.server.internal.eureka; -import com.jayway.jsonpath.JsonPath; -import com.linecorp.armeria.server.Server; -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import java.util.Collections; +import okhttp3.HttpUrl; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import zipkin.server.ZipkinServer; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.testcontainers.utility.DockerImageName.parse; +class ITZipkinEureka extends BaseITZipkinEureka { + @Container static EurekaContainer eureka = new EurekaContainer(Collections.emptyMap()); -/** - * Integration test for Eureka, which validates with Eureka REST operations - */ -@SpringBootTest( - classes = ZipkinServer.class, - webEnvironment = SpringBootTest.WebEnvironment.NONE, // RANDOM_PORT requires spring-web - properties = { - "server.port=0", - "spring.config.name=zipkin-server", - "spring.main.banner-mode=off", - "zipkin.storage.type=", // cheat and test empty storage type - "zipkin.collector.http.enabled=false", - "zipkin.query.enabled=false", - "zipkin.ui.enabled=false" - }) -@Tag("docker") -@Testcontainers(disabledWithoutDocker = true) -@TestMethodOrder(OrderAnnotation.class) // so that deregistration tests don't flake the others. -class ITZipkinEureka { - /** - * Path under the Eureka v2 endpoint for the app named "zipkin". - * Note that Eureka always coerces app names to uppercase. - */ - private static final String APPS_ZIPKIN = "/apps/ZIPKIN"; - private final OkHttpClient client = new OkHttpClient.Builder().followRedirects(false).build(); - - @Container static EurekaContainer eureka = new EurekaContainer(); - - @Autowired Server zipkin; + static HttpUrl serviceUrl() { + return eureka.serviceUrl(); + } /** Get the serviceUrl of the Eureka container prior to booting Zipkin. */ @DynamicPropertySource static void propertyOverride(DynamicPropertyRegistry registry) { - registry.add("zipkin.discovery.eureka.serviceUrl", eureka::serviceUrl); - } - - @BeforeEach void awaitRegistration(){ - // The zipkin server may start before Eureka processes the registration - await().until(this::getEurekaZipkinAppAsString, (s) -> true); - } - - @Test @Order(1) void registersInEureka() throws IOException { - String json = getEurekaZipkinAppAsString(); - - // Make sure the health status is OK - assertThat(readString(json, "$.application.instance[0].status")) - .isEqualTo("UP"); - - String zipkinHostname = zipkin.defaultHostname(); - int zipkinPort = zipkin.activePort().localAddress().getPort(); - - // Note: Netflix/Eureka says use hostname, which can conflict on laptops. - // Armeria adopts the spring-cloud-netflix convention shown here. - assertThat(readString(json, "$.application.instance[0].instanceId")) - .isEqualTo(zipkinHostname + ":zipkin:" + zipkinPort); - - // Make sure the vip address is relevant - assertThat(readString(json, "$.application.instance[0].vipAddress")) - .isEqualTo(zipkinHostname + ":" + zipkinPort); - } - - @Test @Order(2) void deregistersOnClose() { - zipkin.close(); - await().untilAsserted( // wait for deregistration - () -> { - try (Response response = getEurekaZipkinApp()) { - assertThat(response.code()).isEqualTo(404); - } - }); - } - - private String getEurekaZipkinAppAsString() throws IOException { - try (Response response = getEurekaZipkinApp(); ResponseBody body = response.body()) { - assertThat(response.isSuccessful()).withFailMessage(response.toString()).isTrue(); - return body != null ? body.string() : ""; - } - } - - private Response getEurekaZipkinApp() throws IOException { - return client.newCall(new Request.Builder() - .url(eureka.serviceUrl() + APPS_ZIPKIN) - .header("Accept", "application/json") // XML is default - .build()).execute(); - } - - private static final class EurekaContainer extends GenericContainer { - static final int EUREKA_PORT = 8761; - - EurekaContainer() { - super(parse("ghcr.io/openzipkin/zipkin-eureka:3.0.1")); - withExposedPorts(EUREKA_PORT); - waitStrategy = Wait.forHealthcheck(); - withStartupTimeout(Duration.ofSeconds(60)); - } - - String serviceUrl() { - return "http://" + getHost() + ":" + getMappedPort(EUREKA_PORT) + "/eureka/v2"; - } + registry.add("zipkin.discovery.eureka.serviceUrl", () -> serviceUrl().url()); } - static String readString(String json, String jsonPath) { - return JsonPath.compile(jsonPath).read(json); + ITZipkinEureka() { + super(serviceUrl()); } } diff --git a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEurekaAuthenticated.java b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEurekaAuthenticated.java new file mode 100644 index 00000000000..3550a254ca0 --- /dev/null +++ b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ITZipkinEurekaAuthenticated.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.server.internal.eureka; + +import java.util.Map; +import okhttp3.HttpUrl; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.junit.jupiter.Container; + +class ITZipkinEurekaAuthenticated extends BaseITZipkinEureka { + static final String username = "user", password = "pass"; + @Container static EurekaContainer eureka = + new EurekaContainer(Map.of("EUREKA_USERNAME", username, "EUREKA_PASSWORD", password)); + + static HttpUrl serviceUrl() { + return eureka.serviceUrl().newBuilder().username(username).password(password).build(); + } + + /** Get the serviceUrl of the Eureka container prior to booting Zipkin. */ + @DynamicPropertySource static void propertyOverride(DynamicPropertyRegistry registry) { + registry.add("zipkin.discovery.eureka.serviceUrl", () -> serviceUrl().url()); + } + + ITZipkinEurekaAuthenticated() { + super(serviceUrl()); + } +} diff --git a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryConfigurationTest.java b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryConfigurationTest.java index e3dcce86929..159af81df44 100644 --- a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryConfigurationTest.java +++ b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryConfigurationTest.java @@ -44,7 +44,7 @@ class ZipkinEurekaDiscoveryConfigurationTest { }); } - @Test void providesDiscoveryComponent_whenServiceUrlEmptyString() { + @Test void doesNotProvidesEurekaUpdatingListener_whenServiceUrlEmptyString() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() -> { TestPropertyValues.of("zipkin.discovery.eureka.service-url:").applyTo(context); context.register( @@ -68,6 +68,19 @@ class ZipkinEurekaDiscoveryConfigurationTest { assertThat(context.getBean(EurekaUpdatingListener.class)).isNotNull(); } + @Test void providesDiscoveryComponent_whenServiceUrlAuthenticates() { + TestPropertyValues.of( + "zipkin.discovery.eureka.service-url:http://myuser:mypassword@localhost:8761/eureka/v2") + .applyTo(context); + context.register( + PropertyPlaceholderAutoConfiguration.class, + ZipkinEurekaDiscoveryConfiguration.class, + InMemoryConfiguration.class); + context.refresh(); + + assertThat(context.getBean(EurekaUpdatingListener.class)).isNotNull(); + } + @Test void doesNotProvidesEurekaUpdatingListener_whenServiceUrlSetAndDisabled() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() -> { TestPropertyValues.of("zipkin.discovery.eureka.service-url:http://localhost:8761/eureka/v2") diff --git a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryPropertiesTest.java b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryPropertiesTest.java index 87870f5e6ff..226799bcc75 100644 --- a/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryPropertiesTest.java +++ b/zipkin-server/src/test/java/zipkin2/server/internal/eureka/ZipkinEurekaDiscoveryPropertiesTest.java @@ -13,14 +13,16 @@ */ package zipkin2.server.internal.eureka; +import com.linecorp.armeria.common.auth.BasicToken; +import java.net.URI; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class ZipkinEurekaDiscoveryPropertiesTest { @Test void stringPropertiesConvertEmptyStringsToNull() { - final ZipkinEurekaDiscoveryProperties properties = new ZipkinEurekaDiscoveryProperties(); - properties.setServiceUrl(""); + ZipkinEurekaDiscoveryProperties properties = new ZipkinEurekaDiscoveryProperties(); + properties.setServiceUrl(URI.create("")); properties.setAppName(""); properties.setInstanceId(""); properties.setHostname(""); @@ -28,5 +30,26 @@ class ZipkinEurekaDiscoveryPropertiesTest { assertThat(properties.getAppName()).isNull(); assertThat(properties.getInstanceId()).isNull(); assertThat(properties.getHostname()).isNull(); + assertThat(properties).extracting("auth").isNull(); + } + + // Less logic to strip than fail on unlikely, but invalid input + @Test void setServiceUrl_stripsQueryAndFragment() { + ZipkinEurekaDiscoveryProperties properties = new ZipkinEurekaDiscoveryProperties(); + properties.setServiceUrl(URI.create("http://localhost:8761/eureka/v2?q1=v1#toc")); + + assertThat(properties.getServiceUrl()) + .isEqualTo(URI.create("http://localhost:8761/eureka/v2")); + assertThat(properties).extracting("auth").isNull(); + } + + @Test void setServiceUrl_extractsBasicToken() { + ZipkinEurekaDiscoveryProperties properties = new ZipkinEurekaDiscoveryProperties(); + properties.setServiceUrl(URI.create("http://myuser:mypassword@localhost:8761/eureka/v2")); + + assertThat(properties.getServiceUrl()) + .isEqualTo(URI.create("http://localhost:8761/eureka/v2")); + assertThat(properties).extracting("auth") + .isEqualTo(BasicToken.ofBasic("myuser", "mypassword")); } } diff --git a/zipkin-storage/cassandra/src/test/java/zipkin2/storage/cassandra/CassandraStorageExtension.java b/zipkin-storage/cassandra/src/test/java/zipkin2/storage/cassandra/CassandraStorageExtension.java index 57d4361712d..439956ab405 100644 --- a/zipkin-storage/cassandra/src/test/java/zipkin2/storage/cassandra/CassandraStorageExtension.java +++ b/zipkin-storage/cassandra/src/test/java/zipkin2/storage/cassandra/CassandraStorageExtension.java @@ -156,7 +156,7 @@ static boolean poolInFlight(CqlSession session) { // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 static final class CassandraContainer extends GenericContainer { CassandraContainer() { - super(parse("ghcr.io/openzipkin/zipkin-cassandra:3.0.1")); + super(parse("ghcr.io/openzipkin/zipkin-cassandra:3.0.4")); addExposedPort(9042); waitStrategy = Wait.forHealthcheck(); withLogConsumer(new Slf4jLogConsumer(LOGGER)); diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java index cda3a9b0e27..0e0113144fd 100644 --- a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java @@ -127,7 +127,7 @@ String baseUrl() { // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 static final class ElasticsearchContainer extends GenericContainer { ElasticsearchContainer(int majorVersion) { - super(parse("ghcr.io/openzipkin/zipkin-elasticsearch" + majorVersion + ":3.0.1")); + super(parse("ghcr.io/openzipkin/zipkin-elasticsearch" + majorVersion + ":3.0.4")); addExposedPort(9200); waitStrategy = Wait.forHealthcheck(); withLogConsumer(new Slf4jLogConsumer(LOGGER)); diff --git a/zipkin-storage/mysql-v1/src/test/java/zipkin2/storage/mysql/v1/MySQLExtension.java b/zipkin-storage/mysql-v1/src/test/java/zipkin2/storage/mysql/v1/MySQLExtension.java index 85eb4448839..c08901a9088 100644 --- a/zipkin-storage/mysql-v1/src/test/java/zipkin2/storage/mysql/v1/MySQLExtension.java +++ b/zipkin-storage/mysql-v1/src/test/java/zipkin2/storage/mysql/v1/MySQLExtension.java @@ -113,7 +113,7 @@ int port() { // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 static final class MySQLContainer extends GenericContainer { MySQLContainer() { - super(parse("ghcr.io/openzipkin/zipkin-mysql:3.0.1")); + super(parse("ghcr.io/openzipkin/zipkin-mysql:3.0.4")); addExposedPort(3306); waitStrategy = Wait.forHealthcheck(); withLogConsumer(new Slf4jLogConsumer(LOGGER));