From 53f9f35713bc6e933d631fa046c2cff88f6f01c5 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Fri, 26 Apr 2024 11:20:39 -0600 Subject: [PATCH] loadbalancer-experimental: add provider for enabling DefaultLoadBalancer (#2900) Motivation: We want to make it easy for users to enable DefaultLoadBalancer for specific clients and then manipulate it's behavior via system properties so they don't require rebuilding apps to test. Modifications: Add a new package that includes a SingleAddressHttpClientBuilderProvider which enables users to enable DefaultLoadBalancer for clients based on the address used, or all clients if desired. --- .../README.adoc | 55 +++++ .../build.gradle | 34 +++ .../DefaultHttpLoadBalancerProvider.java | 119 +++++++++ .../DefaultLoadBalancerObserver.java | 104 ++++++++ .../DefaultLoadBalancerProviderConfig.java | 233 ++++++++++++++++++ .../experimental/package-info.java | 19 ++ .../loadbalancer/OutlierDetectorConfig.java | 2 +- .../loadbalancer/package-info.java | 3 + settings.gradle | 1 + 9 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 servicetalk-loadbalancer-experimental-provider/README.adoc create mode 100644 servicetalk-loadbalancer-experimental-provider/build.gradle create mode 100644 servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultHttpLoadBalancerProvider.java create mode 100644 servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerObserver.java create mode 100644 servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerProviderConfig.java create mode 100644 servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/package-info.java diff --git a/servicetalk-loadbalancer-experimental-provider/README.adoc b/servicetalk-loadbalancer-experimental-provider/README.adoc new file mode 100644 index 0000000000..33729ac2f3 --- /dev/null +++ b/servicetalk-loadbalancer-experimental-provider/README.adoc @@ -0,0 +1,55 @@ += DefaultLoadBalancer Providers + +This package provides providers for enabling the DefaultLoadBalancer via system properties to allow for easy +experimentation that doesn't require a recompilation of the application. + +> WARNING: this package is only for experimentation and will be removed in the future. + + +== Enabling DefaultLoadBalancer via System Properties + +=== Dynamically Loading the DefaultHttpLoadBalancerProvider + +This package uses the standard providers pattern. To enable the provider you need to both include this package as +part of your application bundle and also include a file in the resources as follows: +``` +resources/META-INF/services/io.servicetalk.http.api.HttpProviders$SingleAddressHttpClientBuilderProvider +``` + +The contents of this must contain the line + +``` +io.servicetalk.loadbalancer.experimental.DefaultHttpLoadBalancerProvider +``` + +=== Targeting Clients for Which to Enable DefaultLoadBalancer + +The `DefaultHttpLoadBalancerProvider` supports enabling the load balancer either for all clients or only a set of +specific clients. Enabling the load balancer for all clients can be done by setting the following system property: + +``` +io.servicetalk.loadbalancer.experimental.clientsEnabledFor=all +``` + +The experimental load balancer can also be enabled for only a subset of clients. This can be done via setting the +system property to a comma separated list: + +``` +io.servicetalk.loadbalancer.experimental.clientsEnabledFor=service1,service2 +``` + +The specific names will depend on how the client is built. If the client is built using a `HostAndPort`, the names are +only the host component. If the client is built using some other unresolved address form then the string representation +of that is used. + +=== Customizing Name Extraction + +The provider depends on the service name for selecting which client to use. If you're using a custom naming system +the default implementation may not be able to decode the unresolved address type to the appropriate name. Custom naming +schemes can be supported by extending the `DefaultHttpLoadBalancerProvider` and overriding the `clientNameFromAddress` +method. Then this custom provider can be added to the service load list as described in the section above. + +=== All Supported Properties + +All system properties contain the prefix "io.servicetalk.loadbalancer.experimental.". A comprehensive list of the +supported properties can be found in the `DefaultLoadBalancerProviderConfig` class for reference. \ No newline at end of file diff --git a/servicetalk-loadbalancer-experimental-provider/build.gradle b/servicetalk-loadbalancer-experimental-provider/build.gradle new file mode 100644 index 0000000000..b7fdcf591a --- /dev/null +++ b/servicetalk-loadbalancer-experimental-provider/build.gradle @@ -0,0 +1,34 @@ +/* + * Copyright © 2024 Apple Inc. and the ServiceTalk project 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. + */ + +apply plugin: "io.servicetalk.servicetalk-gradle-plugin-internal-library" + +dependencies { + implementation platform(project(":servicetalk-dependencies")) + testImplementation enforcedPlatform("org.junit:junit-bom:$junit5Version") + + api project(":servicetalk-client-api") + api project(":servicetalk-concurrent-api") + + implementation project(":servicetalk-annotations") + implementation project(":servicetalk-loadbalancer") + implementation project(":servicetalk-loadbalancer-experimental") + implementation project(":servicetalk-http-api") + implementation project(":servicetalk-http-netty") + implementation project(":servicetalk-utils-internal") + implementation "com.google.code.findbugs:jsr305" + implementation "org.slf4j:slf4j-api" +} diff --git a/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultHttpLoadBalancerProvider.java b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultHttpLoadBalancerProvider.java new file mode 100644 index 0000000000..b88d6e9517 --- /dev/null +++ b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultHttpLoadBalancerProvider.java @@ -0,0 +1,119 @@ +/* + * Copyright © 2024 Apple Inc. and the ServiceTalk project 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 io.servicetalk.loadbalancer.experimental; + +import io.servicetalk.client.api.LoadBalancerFactory; +import io.servicetalk.http.api.DelegatingSingleAddressHttpClientBuilder; +import io.servicetalk.http.api.FilterableStreamingHttpLoadBalancedConnection; +import io.servicetalk.http.api.HttpLoadBalancerFactory; +import io.servicetalk.http.api.HttpProviders; +import io.servicetalk.http.api.SingleAddressHttpClientBuilder; +import io.servicetalk.http.netty.DefaultHttpLoadBalancerFactory; +import io.servicetalk.loadbalancer.LoadBalancers; +import io.servicetalk.transport.api.HostAndPort; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.Objects.requireNonNull; + +/** + * A client builder provider that supports enabling the new `DefaultLoadBalancer` in applications via property flags. + * See the packages README.md for more details. + */ +public class DefaultHttpLoadBalancerProvider implements HttpProviders.SingleAddressHttpClientBuilderProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultHttpLoadBalancerProvider.class); + + private final DefaultLoadBalancerProviderConfig config; + + public DefaultHttpLoadBalancerProvider() { + this(DefaultLoadBalancerProviderConfig.INSTANCE); + } + + // exposed for testing + DefaultHttpLoadBalancerProvider(final DefaultLoadBalancerProviderConfig config) { + this.config = requireNonNull(config, "config"); + } + + @Override + public final SingleAddressHttpClientBuilder newBuilder(U address, + SingleAddressHttpClientBuilder builder) { + final String serviceName = clientNameFromAddress(address); + if (config.enabledForServiceName(serviceName)) { + try { + HttpLoadBalancerFactory loadBalancerFactory = DefaultHttpLoadBalancerFactory.Builder.from( + defaultLoadBalancer(serviceName)).build(); + builder = builder.loadBalancerFactory(loadBalancerFactory); + return new LoadBalancerIgnoringBuilder(builder, serviceName); + } catch (Throwable ex) { + LOGGER.warn("Failed to enabled DefaultLoadBalancer for client to address {}.", address, ex); + } + } + return builder; + } + + private LoadBalancerFactory defaultLoadBalancer( + String serviceName) { + return LoadBalancers. + builder("experimental-load-balancer") + .loadBalancerObserver(new DefaultLoadBalancerObserver(serviceName)) + // set up the new features. + .outlierDetectorConfig(config.outlierDetectorConfig()) + .loadBalancingPolicy(config.getLoadBalancingPolicy()) + .build(); + } + + /** + * Extract the service name from the address object. + * Note: this is a protected method to allow overriding for custom address types. + * @param the unresolved type of the address. + * @param address the address from which to extract the service name. + * @return the String representation of the provided address. + */ + protected String clientNameFromAddress(U address) { + String serviceName; + if (address instanceof HostAndPort) { + serviceName = ((HostAndPort) address).hostName(); + } else if (address instanceof String) { + serviceName = (String) address; + } else { + LOGGER.warn("Unknown service address type={} was provided, " + + "default 'toString()' will be used as serviceName", address.getClass()); + serviceName = address.toString(); + } + return serviceName; + } + + private static final class LoadBalancerIgnoringBuilder + extends DelegatingSingleAddressHttpClientBuilder { + + private final String serviceName; + + LoadBalancerIgnoringBuilder(final SingleAddressHttpClientBuilder delegate, final String serviceName) { + super(delegate); + this.serviceName = serviceName; + } + + @Override + public SingleAddressHttpClientBuilder loadBalancerFactory( + HttpLoadBalancerFactory loadBalancerFactory) { + LOGGER.info("Ignoring http load balancer factory of type {} for client to {} which has " + + "DefaultLoadBalancer enabled.", loadBalancerFactory.getClass(), serviceName); + return this; + } + } +} diff --git a/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerObserver.java b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerObserver.java new file mode 100644 index 0000000000..911d881a64 --- /dev/null +++ b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerObserver.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2024 Apple Inc. and the ServiceTalk project 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 io.servicetalk.loadbalancer.experimental; + +import io.servicetalk.client.api.NoActiveHostException; +import io.servicetalk.client.api.ServiceDiscovererEvent; +import io.servicetalk.loadbalancer.LoadBalancerObserver; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +final class DefaultLoadBalancerObserver implements LoadBalancerObserver { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLoadBalancerObserver.class); + + private final String clientName; + + DefaultLoadBalancerObserver(final String clientName) { + this.clientName = requireNonNull(clientName, "clientName"); + } + + @Override + public HostObserver hostObserver(Object resolvedAddress) { + return new HostObserverImpl(resolvedAddress); + } + + @Override + public void onNoHostsAvailable() { + LOGGER.debug("{}- onNoHostsAvailable()", clientName); + } + + @Override + public void onServiceDiscoveryEvent(Collection> events, int oldHostSetSize, + int newHostSetSize) { + LOGGER.debug("{}- onServiceDiscoveryEvent(events: {}, oldHostSetSize: {}, newHostSetSize: {})", + clientName, events, oldHostSetSize, newHostSetSize); + } + + @Override + public void onNoActiveHostsAvailable(int hostSetSize, NoActiveHostException exception) { + LOGGER.debug("{}- No active hosts available. Host set size: {}.", clientName, hostSetSize, exception); + } + + private final class HostObserverImpl implements HostObserver { + + private final Object resolvedAddress; + + HostObserverImpl(final Object resolvedAddress) { + this.resolvedAddress = resolvedAddress; + } + + @Override + public void onHostMarkedExpired(int connectionCount) { + LOGGER.debug("{}:{}- onHostMarkedExpired(connectionCount: {})", + clientName, resolvedAddress, connectionCount); + } + + @Override + public void onActiveHostRemoved(int connectionCount) { + LOGGER.debug("{}:{}- onActiveHostRemoved(connectionCount: {})", + clientName, resolvedAddress, connectionCount); + } + + @Override + public void onExpiredHostRevived(int connectionCount) { + LOGGER.debug("{}:{}- onExpiredHostRevived(connectionCount: {})", + clientName, resolvedAddress, connectionCount); + } + + @Override + public void onExpiredHostRemoved(int connectionCount) { + LOGGER.debug("{}:{}- onExpiredHostRemoved(connectionCount: {})", + clientName, resolvedAddress, connectionCount); + } + + @Override + public void onHostMarkedUnhealthy(@Nullable Throwable cause) { + LOGGER.debug("{}:{}- onHostMarkedUnhealthy(ex)", clientName, resolvedAddress, cause); + } + + @Override + public void onHostRevived() { + LOGGER.debug("{}:{}- onHostRevived()", clientName, resolvedAddress); + } + } +} diff --git a/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerProviderConfig.java b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerProviderConfig.java new file mode 100644 index 0000000000..206b874c43 --- /dev/null +++ b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/DefaultLoadBalancerProviderConfig.java @@ -0,0 +1,233 @@ +/* + * Copyright © 2024 Apple Inc. and the ServiceTalk project 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 io.servicetalk.loadbalancer.experimental; + +import io.servicetalk.client.api.LoadBalancedConnection; +import io.servicetalk.loadbalancer.LoadBalancingPolicy; +import io.servicetalk.loadbalancer.OutlierDetectorConfig; +import io.servicetalk.loadbalancer.P2CLoadBalancingPolicy; +import io.servicetalk.loadbalancer.RoundRobinLoadBalancingPolicy; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import static java.time.Duration.ZERO; +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofSeconds; +import static java.util.Objects.requireNonNull; + +final class DefaultLoadBalancerProviderConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultHttpLoadBalancerProvider.class); + + private enum LBPolicy { + P2C, + RoundRobin + } + + // This prefix should be applied to all individual properties. + private static final String PROPERTY_PREFIX = "io.servicetalk.loadbalancer.experimental."; + + private static final String PROP_CLIENTS_ENABLED_FOR = "clientsEnabledFor"; + + private static final String PROP_FAILED_CONNECTIONS_THRESHOLD = "healthCheckFailedConnectionsThreshold"; + private static final String PROP_LOAD_BALANCING_POLICY = "policy"; + private static final String PROP_EWMA_HALF_LIFE_MS = "ewmaHalfLifeMs"; + private static final String PROP_CONSECUTIVE_5XX = "consecutive5xx"; + private static final String PROP_INTERVAL_MS = "intervalMs"; + private static final String PROP_BASE_EJECTION_TIME_MS = "baseEjectionTimeMs"; + private static final String PROP_MAX_EJECTION_PERCENT = "maxEjectionPercent"; + private static final String PROP_ENFORCING_CONSECUTIVE_5XX = "enforcingConsecutive5xx"; + private static final String PROP_ENFORCING_SUCCESS_RATE = "enforcingSuccessRate"; + private static final String PROP_SUCCESS_RATE_MIN_HOSTS = "successRateMinimumHosts"; + private static final String PROP_SUCCESS_RATE_REQUEST_VOL = "successRateRequestVolume"; + private static final String PROP_SUCCESS_RATE_STDEV_FACTOR = "successRateStdevFactor"; + private static final String PROP_FAILURE_PERCENTAGE_THRESHOLD = "failurePercentageThreshold"; + private static final String PROP_ENFORCING_FAILURE_PERCENTAGE = "enforcingFailurePercentage"; + private static final String PROP_FAILURE_PERCENTAGE_MIN_HOSTS = "failurePercentageMinimumHosts"; + private static final String PROP_FAILURE_PERCENTAGE_REQUEST_VOL = "failurePercentageRequestVolume"; + private static final String PROP_MAX_EJECTION_TIME_MS = "maxEjectionTimeMs"; + private static final String PROP_EJECTION_TIME_JITTER_MS = "ejectionTimeJitterMs"; + + static final DefaultLoadBalancerProviderConfig INSTANCE = new DefaultLoadBalancerProviderConfig(); + + private final Properties properties; + + private final String rawClientsEnabledFor; + private final Set clientsEnabledFor; + private final int failedConnectionsThreshold; + private final LBPolicy lbPolicy; + private final Duration ewmaHalfLife; + private final int consecutive5xx; + private final Duration interval; + private final Duration baseEjectionTime; + private final int maxEjectionPercentage; + private final int enforcingConsecutive5xx; + private final int enforcingSuccessRate; + private final int successRateMinimumHosts; + private final int successRateRequestVolume; + private final int successRateStdevFactor; + private final int failurePercentageThreshold; + private final int enforcingFailurePercentage; + private final int failurePercentageMinimumHosts; + private final int failurePercentageRequestVolume; + private final Duration maxEjectionTime; + private final Duration ejectionTimeJitter; + + private DefaultLoadBalancerProviderConfig() { + this(System.getProperties()); + } + + private DefaultLoadBalancerProviderConfig(Properties properties) { + this.properties = requireNonNull(properties, "properties"); + rawClientsEnabledFor = getString(PROP_CLIENTS_ENABLED_FOR, "").trim(); + clientsEnabledFor = getClientsEnabledFor(rawClientsEnabledFor); + failedConnectionsThreshold = getInt(PROP_FAILED_CONNECTIONS_THRESHOLD, 5 /*ST default*/); + lbPolicy = getLBPolicy(); + ewmaHalfLife = ofMillis(getLong(PROP_EWMA_HALF_LIFE_MS, ofSeconds(10).toMillis())); + consecutive5xx = getInt(PROP_CONSECUTIVE_5XX, 5); + interval = ofMillis(getLong(PROP_INTERVAL_MS, ofSeconds(10).toMillis())); + baseEjectionTime = ofMillis(getLong(PROP_BASE_EJECTION_TIME_MS, ofSeconds(30).toMillis())); + maxEjectionPercentage = getInt(PROP_MAX_EJECTION_PERCENT, 20); + enforcingConsecutive5xx = getInt(PROP_ENFORCING_CONSECUTIVE_5XX, 100); + enforcingSuccessRate = getInt(PROP_ENFORCING_SUCCESS_RATE, 100); + successRateMinimumHosts = getInt(PROP_SUCCESS_RATE_MIN_HOSTS, 5); + successRateRequestVolume = getInt(PROP_SUCCESS_RATE_REQUEST_VOL, 100); + successRateStdevFactor = getInt(PROP_SUCCESS_RATE_STDEV_FACTOR, 1900); + failurePercentageThreshold = getInt(PROP_FAILURE_PERCENTAGE_THRESHOLD, 85); + enforcingFailurePercentage = getInt(PROP_ENFORCING_FAILURE_PERCENTAGE, 0); + failurePercentageMinimumHosts = getInt(PROP_FAILURE_PERCENTAGE_MIN_HOSTS, 5); + failurePercentageRequestVolume = getInt(PROP_FAILURE_PERCENTAGE_REQUEST_VOL, 50); + maxEjectionTime = ofMillis(getLong(PROP_MAX_EJECTION_TIME_MS, ofSeconds(90).toMillis())); + ejectionTimeJitter = ofMillis(getLong(PROP_EJECTION_TIME_JITTER_MS, ZERO.toMillis())); + } + + private LBPolicy getLBPolicy() { + final String configuredLbName = getString(PROP_LOAD_BALANCING_POLICY, LBPolicy.P2C.name()); + if (configuredLbName.equalsIgnoreCase(LBPolicy.P2C.name())) { + return LBPolicy.P2C; + } else if (configuredLbName.equalsIgnoreCase(LBPolicy.RoundRobin.name())) { + return LBPolicy.RoundRobin; + } else { + LOGGER.warn("Unrecognized load balancer policy name: {}. Defaulting to P2C.", configuredLbName); + return LBPolicy.P2C; + } + } + + private Set getClientsEnabledFor(String propertyValue) { + final Set result = new HashSet<>(); + // if enabled for all there is no need to parse. + if (!"*".equals(propertyValue)) { + for (String serviceName : propertyValue.split(",")) { + String trimmed = serviceName.trim(); + if (!trimmed.isEmpty()) { + result.add(trimmed); + } + } + } + return result; + } + + LoadBalancingPolicy getLoadBalancingPolicy() { + if (lbPolicy == lbPolicy.P2C) { + return new P2CLoadBalancingPolicy.Builder().build(); + } else { + return new RoundRobinLoadBalancingPolicy.Builder().build(); + } + } + + boolean enabledForServiceName(String serviceName) { + return "*".equals(rawClientsEnabledFor) || clientsEnabledFor.contains(serviceName); + } + + OutlierDetectorConfig outlierDetectorConfig() { + return new OutlierDetectorConfig.Builder() + .failedConnectionsThreshold(failedConnectionsThreshold) + .ewmaHalfLife(ewmaHalfLife) + .consecutive5xx(consecutive5xx) + .failureDetectorInterval(interval) + .baseEjectionTime(baseEjectionTime) + .ejectionTimeJitter(ejectionTimeJitter) + .maxEjectionPercentage(maxEjectionPercentage) + .enforcingConsecutive5xx(enforcingConsecutive5xx) + .enforcingSuccessRate(enforcingSuccessRate) + .successRateMinimumHosts(successRateMinimumHosts) + .successRateRequestVolume(successRateRequestVolume) + .successRateStdevFactor(successRateStdevFactor) + .failurePercentageThreshold(failurePercentageThreshold) + .enforcingFailurePercentage(enforcingFailurePercentage) + .failurePercentageMinimumHosts(failurePercentageMinimumHosts) + .failurePercentageRequestVolume(failurePercentageRequestVolume) + .maxEjectionTime(maxEjectionTime) + .build(); + } + + @Override + public String toString() { + return "ExperimentalOutlierDetectorConfig{" + + "clientsEnabledFor=" + rawClientsEnabledFor + + ", failedConnectionsThreshold=" + failedConnectionsThreshold + + ", lbPolicy=" + lbPolicy + + ", ewmaHalfLife=" + ewmaHalfLife + + ", consecutive5xx=" + consecutive5xx + + ", interval=" + interval + + ", baseEjectionTime=" + baseEjectionTime + + ", ejectionTimeJitter=" + ejectionTimeJitter + + ", maxEjectionPercentage=" + maxEjectionPercentage + + ", enforcingConsecutive5xx=" + enforcingConsecutive5xx + + ", enforcingSuccessRate=" + enforcingSuccessRate + + ", successRateMinimumHosts=" + successRateMinimumHosts + + ", successRateRequestVolume=" + successRateRequestVolume + + ", successRateStdevFactor=" + successRateStdevFactor + + ", failurePercentageThreshold=" + failurePercentageThreshold + + ", enforcingFailurePercentage=" + enforcingFailurePercentage + + ", failurePercentageMinimumHosts=" + failurePercentageMinimumHosts + + ", failurePercentageRequestVolume=" + failurePercentageRequestVolume + + ", maxEjectionTime=" + maxEjectionTime + + '}'; + } + + private String getString(String name, String defaultValue) { + return properties.getProperty(PROPERTY_PREFIX + name, defaultValue); + } + + private long getLong(String name, long defaultValue) { + String propertyValue = properties.getProperty(PROPERTY_PREFIX + name); + if (propertyValue == null) { + return defaultValue; + } + try { + return Long.parseLong(propertyValue.trim()); + } catch (Exception ex) { + LOGGER.warn("Exception parsing property {} with value {} to an integral value. Using the default of {}.", + name, propertyValue, defaultValue, ex); + return defaultValue; + } + } + + private int getInt(String name, int defaultValue) { + long value = getLong(name, defaultValue); + if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Integer overflow for value " + name + ": " + value); + } + return (int) value; + } +} diff --git a/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/package-info.java b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/package-info.java new file mode 100644 index 0000000000..dbed0fa5b3 --- /dev/null +++ b/servicetalk-loadbalancer-experimental-provider/src/main/java/io/servicetalk/loadbalancer/experimental/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright © 2024 Apple Inc. and the ServiceTalk project 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. + */ +@ElementsAreNonnullByDefault +package io.servicetalk.loadbalancer.experimental; + +import io.servicetalk.annotations.ElementsAreNonnullByDefault; diff --git a/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/OutlierDetectorConfig.java b/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/OutlierDetectorConfig.java index ddf44e8edb..80f1fabe98 100644 --- a/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/OutlierDetectorConfig.java +++ b/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/OutlierDetectorConfig.java @@ -497,7 +497,7 @@ public Builder serviceDiscoveryResubscribeInterval(Duration interval, Duration j * health checking mechanism. * @return {@code this}. */ - Builder failedConnectionsThreshold(int failedConnectionsThreshold) { + public Builder failedConnectionsThreshold(int failedConnectionsThreshold) { this.failedConnectionsThreshold = failedConnectionsThreshold; if (failedConnectionsThreshold == 0) { throw new IllegalArgumentException("Not valid value: 0"); diff --git a/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/package-info.java b/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/package-info.java index 07dfa84e2d..9370c66ea7 100644 --- a/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/package-info.java +++ b/servicetalk-loadbalancer-experimental/src/main/java/io/servicetalk/loadbalancer/package-info.java @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@ElementsAreNonnullByDefault package io.servicetalk.loadbalancer; + +import io.servicetalk.annotations.ElementsAreNonnullByDefault; diff --git a/settings.gradle b/settings.gradle index 9a6d88cf04..91aeecc523 100755 --- a/settings.gradle +++ b/settings.gradle @@ -88,6 +88,7 @@ include "servicetalk-annotations", "servicetalk-http-utils", "servicetalk-loadbalancer", "servicetalk-loadbalancer-experimental", + "servicetalk-loadbalancer-experimental-provider", "servicetalk-log4j2-mdc", "servicetalk-log4j2-mdc-utils", "servicetalk-logging-api",