diff --git a/CHANGES.txt b/CHANGES.txt index 974b9ceaa..297fcbf41 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ 0.5.0 ----- + * Add sidecar.instance.id Spark conf to append an instanceId query parameter to outbound sidecar requests, fixing 421 errors when Sidecar is behind a load balancer (CASSANALYTICS-177) * Upgrade sidecar version to 0.4.0 * Exclude IP address from RingInstance equality so node replacement does not fail bulk write jobs (CASSANALYTICS-175) * Regenerate bloom filters for CQLSSTableWriter (CASSANALYTICS-167) diff --git a/analytics-sidecar-client-common/src/main/java/org/apache/cassandra/sidecar/common/http/SidecarQueryParamNames.java b/analytics-sidecar-client-common/src/main/java/org/apache/cassandra/sidecar/common/http/SidecarQueryParamNames.java new file mode 100644 index 000000000..6b615d261 --- /dev/null +++ b/analytics-sidecar-client-common/src/main/java/org/apache/cassandra/sidecar/common/http/SidecarQueryParamNames.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.sidecar.common.http; + +/** + * Custom query parameter names for sidecar HTTP requests. + */ +public final class SidecarQueryParamNames +{ + /** + * {@code "instanceId"} query parameter. When present on an outbound sidecar request it carries + * the job-level instance identifier supplied by the client (see the Spark conf key + * {@code spark.cassandra_analytics.sidecar.instance.id}). + * + *

Requires a Sidecar server >= 0.2.0 (see {@code AbstractHandler#host}, introduced in + * CASSSIDECAR-208); older servers do not resolve this parameter and requests will fall back to + * Host-header-based instance resolution. + */ + public static final String INSTANCE_ID = "instanceId"; + + private SidecarQueryParamNames() + { + } +} diff --git a/analytics-sidecar-client/src/main/java/org/apache/cassandra/sidecar/client/HttpClientConfig.java b/analytics-sidecar-client/src/main/java/org/apache/cassandra/sidecar/client/HttpClientConfig.java index 5d2714b3c..ae2042367 100644 --- a/analytics-sidecar-client/src/main/java/org/apache/cassandra/sidecar/client/HttpClientConfig.java +++ b/analytics-sidecar-client/src/main/java/org/apache/cassandra/sidecar/client/HttpClientConfig.java @@ -38,6 +38,7 @@ public class HttpClientConfig public static final String DEFAULT_TRUST_STORE_TYPE = "JKS"; public static final String DEFAULT_KEY_STORE_TYPE = "PKCS12"; public static final String DEFAULT_CASSANDRA_ROLE = null; + public static final Integer DEFAULT_INSTANCE_ID = null; private final long timeoutMillis; private final boolean ssl; @@ -54,6 +55,7 @@ public class HttpClientConfig private final String keyStorePassword; private final String keyStoreType; private final String cassandraRole; + private final Integer instanceId; private HttpClientConfig(Builder builder) { @@ -72,6 +74,7 @@ private HttpClientConfig(Builder builder) keyStorePassword = builder.keyStorePassword; keyStoreType = builder.keyStoreType; cassandraRole = builder.cassandraRole; + instanceId = builder.instanceId; } /** @@ -192,6 +195,15 @@ public String cassandraRole() return cassandraRole; } + /** + * @return the job-level sidecar instance identifier, or {@code null} to omit the {@code instanceId} query parameter + */ + @Nullable + public Integer instanceId() + { + return instanceId; + } + /** * {@code HttpClient} builder static inner class. * @@ -214,6 +226,7 @@ public static class Builder> private String keyStorePassword; private String keyStoreType = DEFAULT_KEY_STORE_TYPE; private String cassandraRole = DEFAULT_CASSANDRA_ROLE; + private Integer instanceId = DEFAULT_INSTANCE_ID; /** * @return a reference to itself @@ -412,6 +425,26 @@ public T cassandraRole(String cassandraRole) return self(); } + /** + * Sets the {@code instanceId} query parameter appended to every outbound sidecar request, + * and returns a reference to this Builder enabling method chaining. Non-null values must + * be greater than or equal to {@code 0}. + * + * @param instanceId the {@code instanceId} to set, or {@code null} to disable it + * @return a reference to this Builder + */ + public T instanceId(Integer instanceId) + { + // Re-validated in BulkSparkConf.getSidecarInstanceId() to surface a Spark-conf-specific + // error message early; keep this constraint (>= 0) in sync with that check. + if (instanceId != null && instanceId < 0) + { + throw new IllegalArgumentException("instanceId must be greater than or equal to 0"); + } + this.instanceId = instanceId; + return self(); + } + /** * Returns a {@code SidecarClientConfig} built from the parameters previously set. * diff --git a/analytics-sidecar-client/src/test/java/org/apache/cassandra/sidecar/client/HttpClientConfigTest.java b/analytics-sidecar-client/src/test/java/org/apache/cassandra/sidecar/client/HttpClientConfigTest.java index a2c1db659..0a839a65d 100644 --- a/analytics-sidecar-client/src/test/java/org/apache/cassandra/sidecar/client/HttpClientConfigTest.java +++ b/analytics-sidecar-client/src/test/java/org/apache/cassandra/sidecar/client/HttpClientConfigTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; /** @@ -159,4 +160,40 @@ void testCassandraRole() HttpClientConfig config = new HttpClientConfig.Builder<>().cassandraRole("custom_role").build(); assertThat(config.cassandraRole()).isEqualTo("custom_role"); } + + @Test + void testInstanceIdDefaultIsNull() + { + HttpClientConfig config = new HttpClientConfig.Builder<>().build(); + assertThat(config.instanceId()).isNull(); + } + + @Test + void testInstanceId() + { + HttpClientConfig config = new HttpClientConfig.Builder<>().instanceId(42).build(); + assertThat(config.instanceId()).isEqualTo(42); + } + + @Test + void testInstanceIdZeroIsAllowed() + { + HttpClientConfig config = new HttpClientConfig.Builder<>().instanceId(0).build(); + assertThat(config.instanceId()).isEqualTo(0); + } + + @Test + void testInstanceIdNullDisablesIt() + { + HttpClientConfig config = new HttpClientConfig.Builder<>().instanceId(null).build(); + assertThat(config.instanceId()).isNull(); + } + + @Test + void testInstanceIdNegativeThrows() + { + assertThatThrownBy(() -> new HttpClientConfig.Builder<>().instanceId(-1)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("instanceId must be greater than or equal to 0"); + } } diff --git a/analytics-sidecar-vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java b/analytics-sidecar-vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java index f4c3890df..f4e455dec 100644 --- a/analytics-sidecar-vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java +++ b/analytics-sidecar-vertx-client/src/main/java/org/apache/cassandra/sidecar/client/VertxHttpClient.java @@ -58,6 +58,7 @@ import org.apache.cassandra.sidecar.common.request.UploadableRequest; import static org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.AUTH_ROLE; +import static org.apache.cassandra.sidecar.common.http.SidecarQueryParamNames.INSTANCE_ID; import static org.apache.cassandra.sidecar.common.utils.StringUtils.isNullOrEmpty; /** @@ -252,6 +253,13 @@ protected HttpRequest vertxRequest(SidecarInstance sidecarInstance, Requ sidecarInstance.hostname(), request.requestURI()); + if (config.instanceId() != null) + { + vertxRequest = vertxRequest.addQueryParam(INSTANCE_ID, String.valueOf(config.instanceId())); + LOGGER.debug("Appended {}={} to request uri. originalUri={}, finalUri={}", + INSTANCE_ID, config.instanceId(), request.requestURI(), vertxRequest.uri()); + } + vertxRequest = applyHeaders(vertxRequest, request.headers()); Map customHeaders = context.customHeaders(); diff --git a/analytics-sidecar-vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxHttpClientTest.java b/analytics-sidecar-vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxHttpClientTest.java index 8d8d0952d..8d1a4a125 100644 --- a/analytics-sidecar-vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxHttpClientTest.java +++ b/analytics-sidecar-vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxHttpClientTest.java @@ -22,11 +22,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import io.netty.handler.codec.http.HttpMethod; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.ext.web.client.HttpRequest; +import org.apache.cassandra.sidecar.common.request.Request; + import static org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.AUTH_ROLE; +import static org.apache.cassandra.sidecar.common.http.SidecarQueryParamNames.INSTANCE_ID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -56,16 +60,61 @@ public void testAuthHeaderSet() HttpClientConfig config = httpClientConfigBuilder().cassandraRole("custom_role").build(); try (VertxHttpClient client = new VertxHttpClient(vertx, config)) { - SidecarInstance instance = mock(SidecarInstance.class); - when(instance.port()).thenReturn(9043); - when(instance.hostname()).thenReturn("localhost"); RequestContext context = new RequestContext.Builder().ringRequest().build(); - HttpRequest request = client.vertxRequest(instance, context); + HttpRequest request = client.vertxRequest(mockInstance(), context); assertThat(request.headers()).isNotEmpty(); assertThat(request.headers().get(AUTH_ROLE)).isEqualTo("custom_role"); } } + @Test + public void testInstanceIdQueryParamAppended() + { + HttpClientConfig config = httpClientConfigBuilder().instanceId(42).build(); + try (VertxHttpClient client = new VertxHttpClient(vertx, config)) + { + RequestContext context = new RequestContext.Builder().ringRequest().build(); + HttpRequest request = client.vertxRequest(mockInstance(), context); + assertThat(request.queryParams().get(INSTANCE_ID)).isEqualTo("42"); + } + } + + @Test + public void testInstanceIdQueryParamNotAppendedWhenNull() + { + HttpClientConfig config = httpClientConfigBuilder().build(); + try (VertxHttpClient client = new VertxHttpClient(vertx, config)) + { + RequestContext context = new RequestContext.Builder().ringRequest().build(); + HttpRequest request = client.vertxRequest(mockInstance(), context); + assertThat(request.queryParams().contains(INSTANCE_ID)).isFalse(); + } + } + + @Test + public void testInstanceIdQueryParamAppendedWithExistingQueryParams() + { + HttpClientConfig config = httpClientConfigBuilder().instanceId(7).build(); + try (VertxHttpClient client = new VertxHttpClient(vertx, config)) + { + Request mockRequest = mock(Request.class); + when(mockRequest.method()).thenReturn(HttpMethod.GET); + when(mockRequest.requestURI()).thenReturn("/api/v1/ring?existingParam=value"); + RequestContext context = new RequestContext.Builder().request(mockRequest).build(); + HttpRequest request = client.vertxRequest(mockInstance(), context); + assertThat(request.queryParams().get("existingParam")).isEqualTo("value"); + assertThat(request.queryParams().get(INSTANCE_ID)).isEqualTo("7"); + } + } + + private SidecarInstance mockInstance() + { + SidecarInstance instance = mock(SidecarInstance.class); + when(instance.port()).thenReturn(9043); + when(instance.hostname()).thenReturn("localhost"); + return instance; + } + private HttpClientConfig.Builder httpClientConfigBuilder() { return new HttpClientConfig.Builder<>() diff --git a/cassandra-analytics-core/src/main/java/org/apache/cassandra/clients/AnalyticsSidecarClient.java b/cassandra-analytics-core/src/main/java/org/apache/cassandra/clients/AnalyticsSidecarClient.java index 2096a60a9..d198a5abd 100644 --- a/cassandra-analytics-core/src/main/java/org/apache/cassandra/clients/AnalyticsSidecarClient.java +++ b/cassandra-analytics-core/src/main/java/org/apache/cassandra/clients/AnalyticsSidecarClient.java @@ -19,6 +19,9 @@ package org.apache.cassandra.clients; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import o.a.c.sidecar.client.shaded.io.vertx.core.Vertx; import o.a.c.sidecar.client.shaded.io.vertx.core.VertxOptions; import o.a.c.sidecar.client.shaded.client.HttpClientConfig; @@ -36,6 +39,8 @@ public class AnalyticsSidecarClient { + private static final Logger LOGGER = LoggerFactory.getLogger(AnalyticsSidecarClient.class); + private AnalyticsSidecarClient() { } @@ -45,20 +50,12 @@ public static SidecarClient from(SidecarInstancesProvider sidecarInstancesProvid Vertx vertx = Vertx.vertx(new VertxOptions().setUseDaemonThread(true) .setWorkerPoolSize(conf.getMaxHttpConnections())); - String userAgent = transportModeBasedWriterUserAgent(conf.getTransportInfo().getTransport()); - HttpClientConfig httpClientConfig = new HttpClientConfig.Builder<>() - .timeoutMillis(conf.getHttpResponseTimeoutMs()) - .idleTimeoutMillis(conf.getHttpConnectionTimeoutMs()) - .userAgent(userAgent) - .keyStoreInputStream(conf.getKeyStore()) - .keyStorePassword(conf.getKeyStorePassword()) - .keyStoreType(conf.getKeyStoreTypeOrDefault()) - .trustStoreInputStream(conf.getTrustStore()) - .trustStorePassword(conf.getTrustStorePasswordOrDefault()) - .trustStoreType(conf.getTrustStoreTypeOrDefault()) - .ssl(conf.hasKeystoreAndKeystorePassword()) - .cassandraRole(conf.getCassandraRole()) - .build(); + HttpClientConfig httpClientConfig = buildHttpClientConfig(conf); + if (httpClientConfig.instanceId() != null) + { + LOGGER.info("Sidecar HTTP client configured with instanceId={} (applied to every outbound sidecar request)", + httpClientConfig.instanceId()); + } StartupValidator.instance().register(new SslValidation(conf)); StartupValidator.instance().register(new BulkWriterKeyStoreValidation(conf)); @@ -74,6 +71,25 @@ public static SidecarClient from(SidecarInstancesProvider sidecarInstancesProvid return Sidecar.buildClient(sidecarConfig, vertx, httpClientConfig, sidecarInstancesProvider); } + static HttpClientConfig buildHttpClientConfig(BulkSparkConf conf) + { + String userAgent = transportModeBasedWriterUserAgent(conf.getTransportInfo().getTransport()); + return new HttpClientConfig.Builder<>() + .timeoutMillis(conf.getHttpResponseTimeoutMs()) + .idleTimeoutMillis(conf.getHttpConnectionTimeoutMs()) + .userAgent(userAgent) + .keyStoreInputStream(conf.getKeyStore()) + .keyStorePassword(conf.getKeyStorePassword()) + .keyStoreType(conf.getKeyStoreTypeOrDefault()) + .trustStoreInputStream(conf.getTrustStore()) + .trustStorePassword(conf.getTrustStorePasswordOrDefault()) + .trustStoreType(conf.getTrustStoreTypeOrDefault()) + .ssl(conf.hasKeystoreAndKeystorePassword()) + .cassandraRole(conf.getCassandraRole()) + .instanceId(conf.getSidecarInstanceId()) + .build(); + } + static String transportModeBasedWriterUserAgent(DataTransport transport) { switch (transport) diff --git a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConf.java b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConf.java index a01c2d1e3..02b0e92ad 100644 --- a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConf.java +++ b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConf.java @@ -125,6 +125,7 @@ public class BulkSparkConf implements Serializable public static final String SIDECAR_REQUEST_RETRY_DELAY_MILLIS = SETTING_PREFIX + "sidecar.request.retries.delay.milliseconds"; public static final String SIDECAR_REQUEST_MAX_RETRY_DELAY_MILLIS = SETTING_PREFIX + "sidecar.request.retries.max.delay.milliseconds"; public static final String SIDECAR_REQUEST_TIMEOUT_SECONDS = SETTING_PREFIX + "sidecar.request.timeout.seconds"; + public static final String SIDECAR_INSTANCE_ID = SETTING_PREFIX + "sidecar.instance.id"; public static final String SKIP_CLEAN = SETTING_PREFIX + "job.skip_clean"; public static final String USE_OPENSSL = SETTING_PREFIX + "use_openssl"; // defines the max number of consecutive retries allowed in the ring monitor @@ -579,6 +580,21 @@ public int getSidecarRequestTimeoutSeconds() return getInt(SIDECAR_REQUEST_TIMEOUT_SECONDS, DEFAULT_SIDECAR_REQUEST_TIMEOUT_SECONDS); } + @Nullable + public Integer getSidecarInstanceId() + { + Integer value = getOptionalInt(SIDECAR_INSTANCE_ID).orElse(null); + // Validated here (not just in HttpClientConfig.Builder.instanceId()) to surface a + // Spark-conf-specific error message before any HTTP client is constructed. Keep this + // constraint (>= 0) in sync with that check. + if (value != null && value < 0) + { + throw new IllegalArgumentException("Spark conf " + SIDECAR_INSTANCE_ID + + " must be a non-negative integer; got " + value); + } + return value; + } + public int getHttpConnectionTimeoutMs() { return getInt(HTTP_CONNECTION_TIMEOUT, DEFAULT_HTTP_CONNECTION_TIMEOUT); diff --git a/cassandra-analytics-core/src/test/java/org/apache/cassandra/clients/AnalyticsSidecarClientTest.java b/cassandra-analytics-core/src/test/java/org/apache/cassandra/clients/AnalyticsSidecarClientTest.java new file mode 100644 index 000000000..641df2432 --- /dev/null +++ b/cassandra-analytics-core/src/test/java/org/apache/cassandra/clients/AnalyticsSidecarClientTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.clients; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.junit.jupiter.api.Test; + +import o.a.c.sidecar.client.shaded.client.HttpClientConfig; +import org.apache.cassandra.spark.bulkwriter.BulkSparkConf; +import org.apache.cassandra.spark.bulkwriter.WriterOptions; +import org.apache.spark.SparkConf; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link AnalyticsSidecarClient} + */ +class AnalyticsSidecarClientTest +{ + @Test + void testBuildHttpClientConfigDefaultInstanceIdIsNull() + { + BulkSparkConf conf = new BulkSparkConf(new SparkConf(), defaultOptions()); + HttpClientConfig httpClientConfig = AnalyticsSidecarClient.buildHttpClientConfig(conf); + assertThat(httpClientConfig.instanceId()).isNull(); + } + + @Test + void testBuildHttpClientConfigWiresInstanceId() + { + SparkConf sparkConf = new SparkConf().set(BulkSparkConf.SIDECAR_INSTANCE_ID, "9"); + BulkSparkConf conf = new BulkSparkConf(sparkConf, defaultOptions()); + HttpClientConfig httpClientConfig = AnalyticsSidecarClient.buildHttpClientConfig(conf); + assertThat(httpClientConfig.instanceId()).isEqualTo(9); + } + + private Map defaultOptions() + { + Map options = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); + options.put(WriterOptions.SIDECAR_CONTACT_POINTS.name(), "127.0.0.1"); + options.put(WriterOptions.KEYSPACE.name(), "ks"); + options.put(WriterOptions.TABLE.name(), "table"); + options.put(WriterOptions.KEYSTORE_PASSWORD.name(), "dummy_password"); + // Base64 of "dummy"; getKeyStore() only decodes it, it never parses the bytes as a real keystore. + options.put(WriterOptions.KEYSTORE_BASE64_ENCODED.name(), "ZHVtbXk="); + return options; + } +} diff --git a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConfTest.java b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConfTest.java index 944f0a186..24a7ea5d1 100644 --- a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConfTest.java +++ b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/BulkSparkConfTest.java @@ -169,6 +169,46 @@ void testSkipClean() assertThat(bulkSparkConf.getSkipClean()).isTrue(); } + @Test + void testDefaultSidecarInstanceId() + { + assertThat(bulkSparkConf.getSidecarInstanceId()).isNull(); + } + + @Test + void testSidecarInstanceId() + { + sparkConf.set(BulkSparkConf.SIDECAR_INSTANCE_ID, "3"); + assertThat(bulkSparkConf.getSidecarInstanceId()).isEqualTo(3); + } + + @Test + void testSidecarInstanceIdZeroIsAllowed() + { + sparkConf.set(BulkSparkConf.SIDECAR_INSTANCE_ID, "0"); + assertThat(bulkSparkConf.getSidecarInstanceId()).isEqualTo(0); + } + + @Test + void testSidecarInstanceIdNegativeThrows() + { + sparkConf.set(BulkSparkConf.SIDECAR_INSTANCE_ID, "-1"); + assertThatThrownBy(() -> bulkSparkConf.getSidecarInstanceId()) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Spark conf " + BulkSparkConf.SIDECAR_INSTANCE_ID + + " must be a non-negative integer; got -1"); + } + + @Test + void testSidecarInstanceIdNonIntegerThrows() + { + sparkConf.set(BulkSparkConf.SIDECAR_INSTANCE_ID, "notanint"); + assertThatThrownBy(() -> bulkSparkConf.getSidecarInstanceId()) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Spark conf " + BulkSparkConf.SIDECAR_INSTANCE_ID + + " is not set to a valid integer string"); + } + @Test void testDefaultSidecarPort() {