|
20 | 20 | import static com.google.common.base.Preconditions.checkState;
|
21 | 21 |
|
22 | 22 | import com.google.common.annotations.VisibleForTesting;
|
| 23 | +import com.google.common.base.MoreObjects; |
| 24 | +import com.google.common.base.Objects; |
23 | 25 | import io.grpc.ConnectivityState;
|
24 | 26 | import io.grpc.ConnectivityStateInfo;
|
25 | 27 | import io.grpc.ExperimentalApi;
|
26 | 28 | import io.grpc.LoadBalancer;
|
| 29 | +import io.grpc.LoadBalancerRegistry; |
| 30 | +import io.grpc.NameResolver.ConfigOrError; |
27 | 31 | import io.grpc.Status;
|
| 32 | +import io.grpc.internal.ServiceConfigUtil; |
| 33 | +import java.util.List; |
| 34 | +import java.util.Map; |
28 | 35 | import javax.annotation.Nullable;
|
29 | 36 | import javax.annotation.concurrent.NotThreadSafe;
|
30 | 37 |
|
|
33 | 40 | * other than READY, the new policy will be swapped into place immediately. Otherwise, the channel
|
34 | 41 | * will keep using the old policy until the new policy reports READY or the old policy exits READY.
|
35 | 42 | *
|
36 |
| - * <p>The balancer must {@link #switchTo(LoadBalancer.Factory) switch to} a policy prior to {@link |
| 43 | + * <p>The child balancer and configuration is specified using service config. Config objects are |
| 44 | + * generally created by calling {@link #parseLoadBalancingPolicyConfig(List)} from a |
| 45 | + * {@link io.grpc.LoadBalancerProvider#parseLoadBalancingPolicyConfig |
| 46 | + * provider's parseLoadBalancingPolicyConfig()} implementation. |
| 47 | + * |
| 48 | + * <p>Alternatively, the balancer may {@link #switchTo(LoadBalancer.Factory) switch to} a policy |
| 49 | + * prior to {@link |
37 | 50 | * LoadBalancer#handleResolvedAddresses(ResolvedAddresses) handling resolved addresses} for the
|
38 |
| - * first time. |
| 51 | + * first time. This causes graceful switch to ignore the service config and pass through the |
| 52 | + * resolved addresses directly to the child policy. |
39 | 53 | */
|
40 | 54 | @ExperimentalApi("https://github.com/grpc/grpc-java/issues/5999")
|
41 | 55 | @NotThreadSafe // Must be accessed in SynchronizationContext
|
@@ -90,18 +104,51 @@ public String toString() {
|
90 | 104 | private LoadBalancer pendingLb = defaultBalancer;
|
91 | 105 | private ConnectivityState pendingState;
|
92 | 106 | private SubchannelPicker pendingPicker;
|
| 107 | + private boolean switchToCalled; |
93 | 108 |
|
94 | 109 | private boolean currentLbIsReady;
|
95 | 110 |
|
96 | 111 | public GracefulSwitchLoadBalancer(Helper helper) {
|
97 | 112 | this.helper = checkNotNull(helper, "helper");
|
98 | 113 | }
|
99 | 114 |
|
| 115 | + @Override |
| 116 | + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { |
| 117 | + if (switchToCalled) { |
| 118 | + delegate().handleResolvedAddresses(resolvedAddresses); |
| 119 | + return; |
| 120 | + } |
| 121 | + Config config = (Config) resolvedAddresses.getLoadBalancingPolicyConfig(); |
| 122 | + switchToInternal(config.childFactory); |
| 123 | + delegate().handleResolvedAddresses( |
| 124 | + resolvedAddresses.toBuilder() |
| 125 | + .setLoadBalancingPolicyConfig(config.childConfig) |
| 126 | + .build()); |
| 127 | + } |
| 128 | + |
| 129 | + @Override |
| 130 | + public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { |
| 131 | + if (switchToCalled) { |
| 132 | + return delegate().acceptResolvedAddresses(resolvedAddresses); |
| 133 | + } |
| 134 | + Config config = (Config) resolvedAddresses.getLoadBalancingPolicyConfig(); |
| 135 | + switchToInternal(config.childFactory); |
| 136 | + return delegate().acceptResolvedAddresses( |
| 137 | + resolvedAddresses.toBuilder() |
| 138 | + .setLoadBalancingPolicyConfig(config.childConfig) |
| 139 | + .build()); |
| 140 | + } |
| 141 | + |
100 | 142 | /**
|
101 | 143 | * Gracefully switch to a new policy defined by the given factory, if the given factory isn't
|
102 | 144 | * equal to the current one.
|
103 | 145 | */
|
104 | 146 | public void switchTo(LoadBalancer.Factory newBalancerFactory) {
|
| 147 | + switchToCalled = true; |
| 148 | + switchToInternal(newBalancerFactory); |
| 149 | + } |
| 150 | + |
| 151 | + private void switchToInternal(LoadBalancer.Factory newBalancerFactory) { |
105 | 152 | checkNotNull(newBalancerFactory, "newBalancerFactory");
|
106 | 153 |
|
107 | 154 | if (newBalancerFactory.equals(pendingBalancerFactory)) {
|
@@ -185,4 +232,86 @@ public void shutdown() {
|
185 | 232 | public String delegateType() {
|
186 | 233 | return delegate().getClass().getSimpleName();
|
187 | 234 | }
|
| 235 | + |
| 236 | + /** |
| 237 | + * Provided a JSON list of LoadBalancingConfigs, parse it into a config to pass to GracefulSwitch. |
| 238 | + */ |
| 239 | + public static ConfigOrError parseLoadBalancingPolicyConfig( |
| 240 | + List<Map<String, ?>> loadBalancingConfigs) { |
| 241 | + return parseLoadBalancingPolicyConfig( |
| 242 | + loadBalancingConfigs, LoadBalancerRegistry.getDefaultRegistry()); |
| 243 | + } |
| 244 | + |
| 245 | + /** |
| 246 | + * Provided a JSON list of LoadBalancingConfigs, parse it into a config to pass to GracefulSwitch. |
| 247 | + */ |
| 248 | + public static ConfigOrError parseLoadBalancingPolicyConfig( |
| 249 | + List<Map<String, ?>> loadBalancingConfigs, LoadBalancerRegistry lbRegistry) { |
| 250 | + List<ServiceConfigUtil.LbConfig> childConfigCandidates = |
| 251 | + ServiceConfigUtil.unwrapLoadBalancingConfigList(loadBalancingConfigs); |
| 252 | + if (childConfigCandidates == null || childConfigCandidates.isEmpty()) { |
| 253 | + return ConfigOrError.fromError( |
| 254 | + Status.INTERNAL.withDescription("No child LB config specified")); |
| 255 | + } |
| 256 | + ConfigOrError selectedConfig = |
| 257 | + ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, lbRegistry); |
| 258 | + if (selectedConfig.getError() != null) { |
| 259 | + Status error = selectedConfig.getError(); |
| 260 | + return ConfigOrError.fromError( |
| 261 | + Status.INTERNAL |
| 262 | + .withCause(error.getCause()) |
| 263 | + .withDescription(error.getDescription()) |
| 264 | + .augmentDescription("Failed to select child config")); |
| 265 | + } |
| 266 | + ServiceConfigUtil.PolicySelection selection = |
| 267 | + (ServiceConfigUtil.PolicySelection) selectedConfig.getConfig(); |
| 268 | + return ConfigOrError.fromConfig( |
| 269 | + createLoadBalancingPolicyConfig(selection.getProvider(), selection.getConfig())); |
| 270 | + } |
| 271 | + |
| 272 | + /** |
| 273 | + * Directly create a config to pass to GracefulSwitch. The object returned is the same as would be |
| 274 | + * found in {@code ConfigOrError.getConfig()}. |
| 275 | + */ |
| 276 | + public static Object createLoadBalancingPolicyConfig( |
| 277 | + LoadBalancer.Factory childFactory, @Nullable Object childConfig) { |
| 278 | + return new Config(childFactory, childConfig); |
| 279 | + } |
| 280 | + |
| 281 | + static final class Config { |
| 282 | + final LoadBalancer.Factory childFactory; |
| 283 | + @Nullable |
| 284 | + final Object childConfig; |
| 285 | + |
| 286 | + public Config(LoadBalancer.Factory childFactory, @Nullable Object childConfig) { |
| 287 | + this.childFactory = checkNotNull(childFactory, "childFactory"); |
| 288 | + this.childConfig = childConfig; |
| 289 | + } |
| 290 | + |
| 291 | + @Override |
| 292 | + public boolean equals(Object o) { |
| 293 | + if (this == o) { |
| 294 | + return true; |
| 295 | + } |
| 296 | + if (!(o instanceof Config)) { |
| 297 | + return false; |
| 298 | + } |
| 299 | + Config that = (Config) o; |
| 300 | + return Objects.equal(childFactory, that.childFactory) |
| 301 | + && Objects.equal(childConfig, that.childConfig); |
| 302 | + } |
| 303 | + |
| 304 | + @Override |
| 305 | + public int hashCode() { |
| 306 | + return Objects.hashCode(childFactory, childConfig); |
| 307 | + } |
| 308 | + |
| 309 | + @Override |
| 310 | + public String toString() { |
| 311 | + return MoreObjects.toStringHelper("GracefulSwitchLoadBalancer.Config") |
| 312 | + .add("childFactory", childFactory) |
| 313 | + .add("childConfig", childConfig) |
| 314 | + .toString(); |
| 315 | + } |
| 316 | + } |
188 | 317 | }
|
0 commit comments