Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

Commit

Permalink
[Conjure Java Runtime] Part 9: Version Selecting Clients (#4260)
Browse files Browse the repository at this point in the history
* Change DDP to PSP

* Version selecting client

* Testing

* Compile

* Finish up VSC

* warning

* Oops

* Add generated changelog entries

* javadoc

* CR comments (small part)

* InstanceAndVersion

* Client versions for target factories

* Fix broken test

* Hackery
  • Loading branch information
jeremyk-91 authored Sep 25, 2019
1 parent 62b7b6b commit a127f80
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 104 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.atlasdb.factory;
package com.palantir.common.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -26,23 +26,23 @@
import com.google.common.reflect.AbstractInvocationHandler;
import com.palantir.exception.NotInitializedException;

public final class DynamicDecoratingProxy<T> extends AbstractInvocationHandler {
private final T decoratedService;
private final T defaultService;
private final Supplier<Boolean> shouldDecorate;
public final class PredicateSwitchedProxy<T> extends AbstractInvocationHandler {
private final T firstService;
private final T secondService;
private final Supplier<Boolean> shouldUseFirstService;

private static final Logger log = LoggerFactory.getLogger(DynamicDecoratingProxy.class);
private static final Logger log = LoggerFactory.getLogger(PredicateSwitchedProxy.class);

private DynamicDecoratingProxy(T decoratedService, T defaultService, Supplier<Boolean> shouldDecorate) {
this.decoratedService = decoratedService;
this.defaultService = defaultService;
this.shouldDecorate = shouldDecorate;
private PredicateSwitchedProxy(T firstService, T secondService, Supplier<Boolean> shouldUseFirstService) {
this.firstService = firstService;
this.secondService = secondService;
this.shouldUseFirstService = shouldUseFirstService;
}

public static <T> T newProxyInstance(
T decoratedService, T defaultService, Supplier<Boolean> shouldDecorate, Class<T> clazz) {
DynamicDecoratingProxy<T> service =
new DynamicDecoratingProxy<>(decoratedService, defaultService, shouldDecorate);
T firstService, T secondService, Supplier<Boolean> shouldUseFirstService, Class<T> clazz) {
PredicateSwitchedProxy<T> service =
new PredicateSwitchedProxy<>(firstService, secondService, shouldUseFirstService);
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[] { clazz },
Expand All @@ -51,7 +51,7 @@ public static <T> T newProxyInstance(

@Override
protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {
Object target = shouldDecorate.get() ? decoratedService : defaultService;
Object target = shouldUseFirstService.get() ? firstService : secondService;
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.common.proxy;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntConsumer;

import org.junit.Test;

public class PredicateSwitchedProxyTest {
private final IntConsumer decorated = mock(IntConsumer.class);
private final IntConsumer delegate = mock(IntConsumer.class);

private final AtomicBoolean atomicBoolean = new AtomicBoolean(false);

private final IntConsumer consumer = PredicateSwitchedProxy.newProxyInstance(
decorated,
delegate,
atomicBoolean::get,
IntConsumer.class);

@Test
public void dynamicallySwitchesCorrectlyForMethodsWithArguments() throws Exception {
consumer.accept(10);
verify(decorated, never()).accept(10);
verify(delegate, times(1)).accept(10);

atomicBoolean.set(true);
consumer.accept(20);
verify(decorated, times(1)).accept(20);
verify(delegate, never()).accept(20);

atomicBoolean.set(false);
consumer.accept(30);
verify(decorated, never()).accept(30);
verify(delegate, times(1)).accept(30);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,13 @@ private static <T> T create(
String userAgent,
boolean limitPayload) {
return AtlasDbHttpClients.createLiveReloadingProxyWithFailover(
metricsManager.getRegistry(),
serverListConfigSupplier, trustContextCreator, proxySelectorCreator, type, userAgent, limitPayload);
metricsManager.getTaggedRegistry(),
serverListConfigSupplier,
trustContextCreator,
proxySelectorCreator,
type,
userAgent,
limitPayload);
}

public static <T> T createInstrumentedService(MetricRegistry metricRegistry, T service, Class<T> serviceClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.palantir.conjure.java.api.config.service.ProxyConfiguration;
import com.palantir.conjure.java.api.config.ssl.SslConfiguration;
import com.palantir.conjure.java.config.ssl.TrustContext;
import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;

public final class AtlasDbHttpClients {
public static final TargetFactory DEFAULT_TARGET_FACTORY = AtlasDbFeignTargetFactory.DEFAULT;
Expand Down Expand Up @@ -105,24 +106,34 @@ public static <T> T createProxyWithFailover(
}

public static <T> T createLiveReloadingProxyWithFailover(
MetricRegistry metricRegistry,
TaggedMetricRegistry taggedMetricRegistry,
Supplier<ServerListConfig> serverListConfigSupplier,
Function<SslConfiguration, TrustContext> trustContextCreator,
Function<ProxyConfiguration, ProxySelector> proxySelectorCreator,
Class<T> type,
String userAgent,
boolean limitPayload) {
return AtlasDbMetrics.instrument(
metricRegistry,
type,
DEFAULT_TARGET_FACTORY.createLiveReloadingProxyWithFailover(
return VersionSelectingClients.createVersionSelectingClient(
taggedMetricRegistry,
// TODO (jkong): Replace the new client with the CJR one; also I wish there was a way to curry stuff
ImmutableInstanceAndVersion.of(DEFAULT_TARGET_FACTORY.createLiveReloadingProxyWithFailover(
serverListConfigSupplier,
trustContextCreator,
proxySelectorCreator,
type,
userAgent,
limitPayload),
MetricRegistry.name(type));
DEFAULT_TARGET_FACTORY.getClientVersion()),
ImmutableInstanceAndVersion.of(DEFAULT_TARGET_FACTORY.createLiveReloadingProxyWithFailover(
serverListConfigSupplier,
trustContextCreator,
proxySelectorCreator,
type,
userAgent,
limitPayload),
DEFAULT_TARGET_FACTORY.getClientVersion()),
() -> 0.0,
type);
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.atlasdb.http;

import java.util.concurrent.ThreadLocalRandom;
import java.util.function.DoubleSupplier;

import org.immutables.value.Value;

import com.codahale.metrics.MetricRegistry;
import com.google.common.collect.ImmutableMap;
import com.palantir.atlasdb.util.AtlasDbMetrics;
import com.palantir.common.proxy.PredicateSwitchedProxy;
import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;

/**
* Factory for randomly selecting from a new and a legacy version of a client, based on a live-reloading probability.
*
* Please note that your clients will run independently; if there are stateful invariants that need to be enforced
* across individual clients, you may need to share state appropriately.
*/
final class VersionSelectingClients {
private static final String CLIENT_VERSION = "clientVersion";

private VersionSelectingClients() {
// No, nein, etc.
}

static <T> T createVersionSelectingClient(
TaggedMetricRegistry taggedMetricRegistry,
InstanceAndVersion<T> newClient,
InstanceAndVersion<T> legacyClient,
DoubleSupplier newClientProbabilitySupplier,
Class<T> clazz) {
T instrumentedNewClient = instrumentWithClientVersionTag(
taggedMetricRegistry, newClient, clazz);
T instrumentedLegacyClient = instrumentWithClientVersionTag(
taggedMetricRegistry, legacyClient, clazz);

return PredicateSwitchedProxy.newProxyInstance(
instrumentedNewClient,
instrumentedLegacyClient,
() -> ThreadLocalRandom.current().nextDouble() < newClientProbabilitySupplier.getAsDouble(),
clazz);
}

private static <T> T instrumentWithClientVersionTag(
TaggedMetricRegistry taggedMetricRegistry,
InstanceAndVersion<T> client,
Class<T> clazz) {
return AtlasDbMetrics.instrumentWithTaggedMetrics(
taggedMetricRegistry,
clazz,
client.client(),
MetricRegistry.name(clazz),
$ -> ImmutableMap.of(CLIENT_VERSION, client.version()));
}

@Value.Immutable
interface InstanceAndVersion<T> {
@Value.Parameter
T client();

@Value.Parameter
String version();
}
}

This file was deleted.

Loading

0 comments on commit a127f80

Please sign in to comment.