Skip to content

Commit a5256ab

Browse files
committed
[native] Validate sidecar function signatures against plugin loaded function signatures at startup
1 parent 977cabe commit a5256ab

File tree

10 files changed

+144
-43
lines changed

10 files changed

+144
-43
lines changed

presto-main-base/src/main/java/com/facebook/presto/metadata/BuiltInPluginFunctionNamespaceManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public BuiltInPluginFunctionNamespaceManager(FunctionAndTypeManager functionAndT
3131
super(functionAndTypeManager);
3232
}
3333

34+
public void triggerConflictCheckWithBuiltInFunctions()
35+
{
36+
checkForNamingConflicts(this.getFunctionsFromDefaultNamespace());
37+
}
38+
3439
@Override
3540
public synchronized void registerBuiltInSpecialFunctions(List<? extends SqlFunction> functions)
3641
{

presto-main-base/src/main/java/com/facebook/presto/metadata/BuiltInSpecialFunctionNamespaceManager.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public abstract class BuiltInSpecialFunctionNamespaceManager
5858
{
5959
protected volatile FunctionMap functions = new FunctionMap();
6060
private final FunctionAndTypeManager functionAndTypeManager;
61-
private final Supplier<FunctionMap> cachedFunctions =
61+
protected final Supplier<FunctionMap> cachedFunctions =
6262
Suppliers.memoize(this::createFunctionMap);
6363
private final LoadingCache<Signature, SpecializedFunctionKey> specializedFunctionKeyCache;
6464
private final LoadingCache<SpecializedFunctionKey, ScalarFunctionImplementation> specializedScalarCache;
@@ -212,12 +212,16 @@ public ScalarFunctionImplementation getScalarFunctionImplementation(FunctionHand
212212
return getScalarFunctionImplementation(((BuiltInFunctionHandle) functionHandle).getSignature());
213213
}
214214

215-
private synchronized FunctionMap createFunctionMap()
215+
protected Collection<? extends SqlFunction> getFunctionsFromDefaultNamespace()
216216
{
217217
Optional<FunctionNamespaceManager<?>> functionNamespaceManager =
218218
functionAndTypeManager.getServingFunctionNamespaceManager(functionAndTypeManager.getDefaultNamespace());
219219
checkArgument(functionNamespaceManager.isPresent(), "Cannot find function namespace for catalog '%s'", functionAndTypeManager.getDefaultNamespace().getCatalogName());
220-
checkForNamingConflicts(functionNamespaceManager.get().listFunctions(Optional.empty(), Optional.empty()));
220+
return functionNamespaceManager.get().listFunctions(Optional.empty(), Optional.empty());
221+
}
222+
223+
private synchronized FunctionMap createFunctionMap()
224+
{
221225
return functions;
222226
}
223227

presto-main-base/src/main/java/com/facebook/presto/metadata/FunctionAndTypeManager.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,12 @@ public SpecializedFunctionKey getSpecializedFunctionKey(Signature signature, Col
979979
return builtInTypeAndFunctionNamespaceManager.doGetSpecializedFunctionKeyForMagicLiteralFunctions(signature, this);
980980
}
981981

982-
public CatalogSchemaName configureDefaultNamespace(String defaultNamespacePrefixString)
982+
public BuiltInPluginFunctionNamespaceManager getBuiltInPluginFunctionNamespaceManager()
983+
{
984+
return builtInPluginFunctionNamespaceManager;
985+
}
986+
987+
private CatalogSchemaName configureDefaultNamespace(String defaultNamespacePrefixString)
983988
{
984989
if (!defaultNamespacePrefixString.matches(DEFAULT_NAMESPACE_PREFIX_PATTERN.pattern())) {
985990
throw new PrestoException(GENERIC_USER_ERROR, format("Default namespace prefix string should be in the form of 'catalog.schema', found: %s", defaultNamespacePrefixString));

presto-main/src/main/java/com/facebook/presto/server/PrestoServer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ public void run()
209209
injector.getInstance(ClientRequestFilterManager.class).loadClientRequestFilters();
210210
injector.getInstance(ExpressionOptimizerManager.class).loadExpressionOptimizerFactories();
211211

212+
injector.getInstance(FunctionAndTypeManager.class)
213+
.getBuiltInPluginFunctionNamespaceManager().triggerConflictCheckWithBuiltInFunctions();
214+
212215
startAssociatedProcesses(injector);
213216

214217
injector.getInstance(Announcer.class).start();

presto-main/src/main/java/com/facebook/presto/server/testing/TestingPrestoServer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,12 @@ public void installCoordinatorPlugin(CoordinatorPlugin plugin)
548548
pluginManager.installCoordinatorPlugin(plugin);
549549
}
550550

551+
public void triggerConflictCheckWithBuiltInFunctions()
552+
{
553+
metadata.getFunctionAndTypeManager()
554+
.getBuiltInPluginFunctionNamespaceManager().triggerConflictCheckWithBuiltInFunctions();
555+
}
556+
551557
public DispatchManager getDispatchManager()
552558
{
553559
return dispatchManager;

presto-native-sidecar-plugin/src/main/java/com/facebook/presto/sidecar/functionNamespace/NativeFunctionDefinitionProvider.java

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,24 @@
1414
package com.facebook.presto.sidecar.functionNamespace;
1515

1616
import com.facebook.airlift.http.client.HttpClient;
17-
import com.facebook.airlift.http.client.HttpUriBuilder;
1817
import com.facebook.airlift.http.client.Request;
1918
import com.facebook.airlift.json.JsonCodec;
2019
import com.facebook.airlift.log.Logger;
2120
import com.facebook.presto.functionNamespace.JsonBasedUdfFunctionMetadata;
2221
import com.facebook.presto.functionNamespace.UdfFunctionSignatureMap;
2322
import com.facebook.presto.sidecar.ForSidecarInfo;
24-
import com.facebook.presto.spi.Node;
2523
import com.facebook.presto.spi.NodeManager;
2624
import com.facebook.presto.spi.PrestoException;
2725
import com.google.common.annotations.VisibleForTesting;
2826
import com.google.common.collect.ImmutableMap;
2927
import com.google.inject.Inject;
3028

31-
import java.net.URI;
3229
import java.util.List;
3330
import java.util.Map;
3431

3532
import static com.facebook.airlift.http.client.JsonResponseHandler.createJsonResponseHandler;
3633
import static com.facebook.airlift.http.client.Request.Builder.prepareGet;
34+
import static com.facebook.presto.builtin.tools.NativeSidecarFunctionRegistryTool.getSidecarLocationOnStartup;
3735
import static com.facebook.presto.spi.StandardErrorCode.INVALID_ARGUMENTS;
3836
import static java.util.Objects.requireNonNull;
3937

@@ -42,27 +40,29 @@ public class NativeFunctionDefinitionProvider
4240
{
4341
private static final Logger log = Logger.get(NativeFunctionDefinitionProvider.class);
4442
private final JsonCodec<Map<String, List<JsonBasedUdfFunctionMetadata>>> nativeFunctionSignatureMapJsonCodec;
45-
private final NodeManager nodeManager;
4643
private final HttpClient httpClient;
47-
private static final String FUNCTION_SIGNATURES_ENDPOINT = "/v1/functions";
44+
private final NativeFunctionNamespaceManagerConfig config;
4845

4946
@Inject
5047
public NativeFunctionDefinitionProvider(
5148
@ForSidecarInfo HttpClient httpClient,
5249
JsonCodec<Map<String, List<JsonBasedUdfFunctionMetadata>>> nativeFunctionSignatureMapJsonCodec,
53-
NodeManager nodeManager)
50+
NativeFunctionNamespaceManagerConfig config)
5451
{
5552
this.nativeFunctionSignatureMapJsonCodec =
5653
requireNonNull(nativeFunctionSignatureMapJsonCodec, "nativeFunctionSignatureMapJsonCodec is null");
57-
this.nodeManager = requireNonNull(nodeManager, "nodeManager is null");
58-
this.httpClient = requireNonNull(httpClient, "typeManager is null");
54+
this.httpClient = requireNonNull(httpClient, "httpClient is null");
55+
this.config = requireNonNull(config, "config is null");
5956
}
6057

6158
@Override
6259
public UdfFunctionSignatureMap getUdfDefinition(NodeManager nodeManager)
6360
{
6461
try {
65-
Request request = prepareGet().setUri(getSidecarLocation()).build();
62+
Request request =
63+
prepareGet().setUri(
64+
getSidecarLocationOnStartup(
65+
nodeManager, config.getSidecarNumRetries(), config.getSidecarRetryDelay().toMillis())).build();
6666
Map<String, List<JsonBasedUdfFunctionMetadata>> nativeFunctionSignatureMap = httpClient.execute(request, createJsonResponseHandler(nativeFunctionSignatureMapJsonCodec));
6767
return new UdfFunctionSignatureMap(ImmutableMap.copyOf(nativeFunctionSignatureMap));
6868
}
@@ -71,15 +71,6 @@ public UdfFunctionSignatureMap getUdfDefinition(NodeManager nodeManager)
7171
}
7272
}
7373

74-
private URI getSidecarLocation()
75-
{
76-
Node sidecarNode = nodeManager.getSidecarNode();
77-
return HttpUriBuilder
78-
.uriBuilderFrom(sidecarNode.getHttpUri())
79-
.appendPath(FUNCTION_SIGNATURES_ENDPOINT)
80-
.build();
81-
}
82-
8374
@VisibleForTesting
8475
public HttpClient getHttpClient()
8576
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.facebook.presto.sidecar.functionNamespace;
15+
16+
import com.facebook.airlift.configuration.Config;
17+
import com.facebook.airlift.configuration.ConfigDescription;
18+
import com.facebook.airlift.units.Duration;
19+
20+
import static java.util.concurrent.TimeUnit.MINUTES;
21+
22+
public class NativeFunctionNamespaceManagerConfig
23+
{
24+
private int sidecarNumRetries = 8;
25+
private Duration sidecarRetryDelay = new Duration(1, MINUTES);
26+
27+
public int getSidecarNumRetries()
28+
{
29+
return sidecarNumRetries;
30+
}
31+
32+
@Config("sidecar.num-retries")
33+
@ConfigDescription("Max times to retry fetching sidecar node")
34+
public NativeFunctionNamespaceManagerConfig setSidecarNumRetries(int sidecarNumRetries)
35+
{
36+
this.sidecarNumRetries = sidecarNumRetries;
37+
return this;
38+
}
39+
40+
public Duration getSidecarRetryDelay()
41+
{
42+
return sidecarRetryDelay;
43+
}
44+
45+
@Config("sidecar.retry-delay")
46+
@ConfigDescription("Cooldown period to retry when fetching sidecar node fails")
47+
public NativeFunctionNamespaceManagerConfig setSidecarRetryDelay(Duration sidecarRetryDelay)
48+
{
49+
this.sidecarRetryDelay = sidecarRetryDelay;
50+
return this;
51+
}
52+
}

presto-native-sidecar-plugin/src/main/java/com/facebook/presto/sidecar/functionNamespace/NativeFunctionNamespaceManagerModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public class NativeFunctionNamespaceManagerModule
3737
implements Module
3838
{
3939
private final String catalogName;
40-
4140
private final NodeManager nodeManager;
4241
private final FunctionMetadataManager functionMetadataManager;
4342

@@ -55,6 +54,7 @@ public void configure(Binder binder)
5554

5655
configBinder(binder).bindConfig(SqlInvokedFunctionNamespaceManagerConfig.class);
5756
configBinder(binder).bindConfig(SqlFunctionLanguageConfig.class);
57+
configBinder(binder).bindConfig(NativeFunctionNamespaceManagerConfig.class);
5858
binder.bind(FunctionDefinitionProvider.class).to(NativeFunctionDefinitionProvider.class).in(SINGLETON);
5959
binder.bind(new TypeLiteral<JsonCodec<Map<String, List<JsonBasedUdfFunctionMetadata>>>>() {})
6060
.toInstance(new JsonCodecFactory().mapJsonCodec(String.class, listJsonCodec(JsonBasedUdfFunctionMetadata.class)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.facebook.presto.sidecar.functionNamespace;
15+
16+
import com.facebook.airlift.units.Duration;
17+
import com.google.common.collect.ImmutableMap;
18+
import org.testng.annotations.Test;
19+
20+
import java.util.Map;
21+
22+
import static com.facebook.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
23+
import static com.facebook.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults;
24+
import static com.facebook.airlift.configuration.testing.ConfigAssertions.recordDefaults;
25+
import static java.util.concurrent.TimeUnit.MINUTES;
26+
27+
public class TestNativeFunctionNamespaceManagerConfig
28+
{
29+
@Test
30+
public void testDefaults()
31+
{
32+
assertRecordedDefaults(recordDefaults(NativeFunctionNamespaceManagerConfig.class)
33+
.setSidecarNumRetries(8)
34+
.setSidecarRetryDelay(new Duration(1, MINUTES)));
35+
}
36+
37+
@Test
38+
public void testExplicitPropertyMappings()
39+
{
40+
Map<String, String> properties = new ImmutableMap.Builder<String, String>()
41+
.put("sidecar.num-retries", "15")
42+
.put("sidecar.retry-delay", "5m")
43+
.build();
44+
45+
NativeFunctionNamespaceManagerConfig expected = new NativeFunctionNamespaceManagerConfig()
46+
.setSidecarNumRetries(15)
47+
.setSidecarRetryDelay(new Duration(5, MINUTES));
48+
49+
assertFullMapping(properties, expected);
50+
}
51+
}

presto-tests/src/test/java/com/facebook/presto/functions/TestPluginLoadedDuplicateSqlInvokedFunctions.java

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@
2222
import org.testng.annotations.Test;
2323

2424
import java.util.Set;
25-
import java.util.regex.Pattern;
2625

27-
import static com.facebook.presto.metadata.BuiltInTypeAndFunctionNamespaceManager.JAVA_BUILTIN_NAMESPACE;
2826
import static com.facebook.presto.testing.TestingSession.testSessionBuilder;
29-
import static java.lang.String.format;
30-
import static org.testng.Assert.fail;
27+
import static org.testng.Assert.assertThrows;
3128

3229
public class TestPluginLoadedDuplicateSqlInvokedFunctions
3330
{
@@ -45,20 +42,6 @@ public void setup()
4542
.build());
4643
}
4744

48-
public void assertInvalidFunction(String expr, String exceptionPattern)
49-
{
50-
try {
51-
client.execute("SELECT " + expr);
52-
fail("Function expected to fail but not");
53-
}
54-
catch (Exception e) {
55-
if (!(e.getMessage().matches(exceptionPattern))) {
56-
fail(format("Expected exception message '%s' to match '%s' but not",
57-
e.getMessage(), exceptionPattern));
58-
}
59-
}
60-
}
61-
6245
private static class TestDuplicateFunctionsPlugin
6346
implements Plugin
6447
{
@@ -71,10 +54,11 @@ public Set<Class<?>> getSqlInvokedFunctions()
7154
}
7255
}
7356

57+
// As soon as we trigger the conflict check with the built-in functions, an error will be thrown if duplicate signatures are found.
58+
// In `PrestoServer.java` this conflict check is triggered implicitly.
7459
@Test
7560
public void testDuplicateFunctionsLoaded()
7661
{
77-
assertInvalidFunction(JAVA_BUILTIN_NAMESPACE + ".modulo(10,3)",
78-
Pattern.quote(format("java.lang.IllegalArgumentException: Function already registered: %s.array_intersect<T>(array(array(T))):array(T)", JAVA_BUILTIN_NAMESPACE)));
62+
assertThrows(IllegalArgumentException.class, () -> server.triggerConflictCheckWithBuiltInFunctions());
7963
}
8064
}

0 commit comments

Comments
 (0)