drill-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From paul-rogers <...@git.apache.org>
Subject [GitHub] drill pull request #574: DRILL-4726: Dynamic UDFs support
Date Thu, 08 Sep 2016 15:23:39 GMT
Github user paul-rogers commented on a diff in the pull request:

    https://github.com/apache/drill/pull/574#discussion_r78028875
  
    --- Diff: exec/java-exec/src/test/java/org/apache/drill/TestDynamicUDFSupport.java ---
    @@ -0,0 +1,292 @@
    +/**
    + * 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
    + * <p/>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p/>
    + * 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.drill;
    +
    +import mockit.NonStrictExpectations;
    +import org.apache.drill.common.config.DrillConfig;
    +import org.apache.drill.common.exceptions.UserRemoteException;
    +import org.apache.drill.common.util.TestTools;
    +import org.apache.drill.exec.ExecConstants;
    +import org.apache.drill.exec.expr.fn.RemoteFunctionRegistry;
    +import org.apache.drill.exec.proto.UserBitShared.Registry;
    +import org.apache.drill.exec.util.JarUtil;
    +import org.apache.hadoop.fs.FileSystem;
    +import org.apache.hadoop.fs.Path;
    +import org.junit.Before;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
    +
    +import java.io.File;
    +import java.io.IOException;
    +import java.util.Properties;
    +
    +import static org.hamcrest.CoreMatchers.containsString;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertThat;
    +import static org.junit.Assert.assertTrue;
    +
    +public class TestDynamicUDFSupport extends BaseTestQuery {
    +
    +  private static final File jars = new File(TestTools.getWorkingPath() + "/src/test/resources/jars");
    +  private static final String jName = "DrillUDF-1.0.jar";
    +  private static String sName = JarUtil.getSourceName(jName);
    +
    +  @Rule
    +  public final TemporaryFolder drillUdfDir = new TemporaryFolder();
    +
    +  @Rule
    +  public final TemporaryFolder base = new TemporaryFolder();
    +
    +  @Before
    +  public void setEnvVariables()
    +  {
    +    new NonStrictExpectations(System.class)
    +    {
    +      {
    +        invoke(System.class, "getenv", "DRILL_UDF_DIR");
    +        returns(drillUdfDir.getRoot().getPath());
    +      }
    +    };
    +  }
    +
    +  @Before
    +  public void setup() {
    +    String root = base.getRoot().getPath();
    +    if (!root.equals(getDrillbitContext().getConfig().getString(ExecConstants.UDF_DIRECTORY_STAGING)))
{
    +    Properties overrideProps = new Properties();
    +      overrideProps.setProperty(ExecConstants.UDF_DIRECTORY_BASE, root);
    +      updateTestCluster(1, DrillConfig.create(overrideProps));
    +    }
    +  }
    +
    +  @Test
    +  public void testSyntax() throws Exception {
    +    test("create function using jar 'jar_name.jar'");
    +    test("drop function using jar 'jar_name.jar'");
    +  }
    +
    +  @Test
    +  public void testEnableDynamicSupport() throws Exception {
    +    try {
    +      test("alter system set `exec.udf.enable_dynamic_support` = true");
    +      test("create function using jar 'jar_name.jar'");
    +      test("drop function using jar 'jar_name.jar'");
    +    } finally {
    +      test("alter system reset `exec.udf.enable_dynamic_support`");
    +    }
    +  }
    +
    +  @Test
    +  public void testDisableDynamicSupport() throws Exception {
    +    try {
    +      test("alter system set `exec.udf.enable_dynamic_support` = false");
    +      String[] actions = new String[] {"create", "drop"};
    +      String query = "%s function using jar 'jar_name.jar'";
    +      for (String action : actions) {
    +        try {
    +          test(String.format(query, action));
    +        } catch (UserRemoteException e) {
    +          assertThat(e.getMessage(), containsString("Dynamic UDFs support is disabled."));
    +        }
    +      }
    +    } finally {
    +      test("alter system reset `exec.udf.enable_dynamic_support`");
    +    }
    +  }
    +
    +  @Test
    +  public void testAbsentJarInStaging() throws Exception {
    +    String jar = "jar_name.jar";
    +    String staging = getDrillbitContext().getRemoteFunctionRegistry().getStagingArea().toUri().getPath();
    +    String summary = String.format("Binary [%s] or source [%s-sources.jar] is absent
in udf staging area [%s].", jar, jar.split("\\.")[0], staging);
    +
    +    testBuilder()
    +        .sqlQuery(String.format("create function using jar '%s'", jar))
    +        .unOrdered()
    +        .baselineColumns("ok", "summary")
    +        .baselineValues(false, summary)
    +        .go();
    +  }
    +
    +  @Test
    +  public void testSuccessfulCreate() throws Exception {
    +    copyJarsToStagingArea();
    +
    +    String summary = "The following UDFs in jar %s have been registered:\n" +
    +        "[name: \"custom_lower\"\n" +
    +        "major_type {\n" +
    +        "  minor_type: VARCHAR\n" +
    +        "  mode: REQUIRED\n" +
    +        "}\n" +
    +        "]";
    +
    +    testBuilder()
    +        .sqlQuery(String.format("create function using jar '%s'", jName))
    +        .unOrdered()
    +        .baselineColumns("ok", "summary")
    +        .baselineValues(true, String.format(summary, jName))
    +        .go();
    +
    +    RemoteFunctionRegistry remoteFunctionRegistry = getDrillbitContext().getRemoteFunctionRegistry();
    +    FileSystem fs = remoteFunctionRegistry.getFs();
    +
    +    assertFalse("Staging area should be empty", fs.listFiles(remoteFunctionRegistry.getStagingArea(),
false).hasNext());
    +    assertFalse("Temporary area should be empty", fs.listFiles(remoteFunctionRegistry.getTmpArea(),
false).hasNext());
    +
    +    assertTrue("Binary should be present in registry area", fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(),
jName)));
    +    assertTrue("Source should be present in registry area", fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(),
sName)));
    +
    +    Registry registry = remoteFunctionRegistry.getRegistry();
    +    assertEquals("Registry should contain one jar", registry.getJarList().size(), 1);
    +    assertEquals(registry.getJar(0).getName(), jName);
    +    test(String.format("drop function using jar '%s'", jName));
    +  }
    +
    +  @Test
    +  public void testDuplicatedJar() throws Exception {
    +    copyJarsToStagingArea();
    +    test(String.format("create function using jar '%s'", jName));
    +    copyJarsToStagingArea();
    +
    +    testBuilder()
    +        .sqlQuery(String.format("create function using jar '%s'", jName))
    +        .unOrdered()
    +        .baselineColumns("ok", "summary")
    +        .baselineValues(false, String.format("Jar with %s name has been already registered",
jName))
    +        .go();
    +
    +    test(String.format("drop function using jar '%s'", jName));
    +  }
    +
    +  @Test
    +  public void testDuplicatedFunctions() throws Exception {
    +    copyJarsToStagingArea();
    +    test(String.format("create function using jar '%s'", jName));
    +    copyJarsToStagingArea();
    +
    +    RemoteFunctionRegistry remoteFunctionRegistry = getDrillbitContext().getRemoteFunctionRegistry();
    +    FileSystem fs = remoteFunctionRegistry.getFs();
    +    String newJar = "DrillUDF-2.0.jar";
    +    String newSource = JarUtil.getSourceName(newJar);
    +    Path renamedBinary = new Path(remoteFunctionRegistry.getStagingArea(), newJar);
    +    Path renamedSource = new Path(remoteFunctionRegistry.getStagingArea(), newSource);
    +    fs.rename(new Path(remoteFunctionRegistry.getStagingArea(), jName), renamedBinary);
    +    fs.rename(new Path(remoteFunctionRegistry.getStagingArea(), sName), renamedSource);
    +
    +    String summary = "Found duplicated function in %s - name: \"custom_lower\"\n" +
    +        "major_type {\n" +
    +        "  minor_type: VARCHAR\n" +
    +        "  mode: REQUIRED\n" +
    +        "}\n";
    +
    +    testBuilder()
    +        .sqlQuery(String.format("create function using jar '%s'", newJar))
    +        .unOrdered()
    +        .baselineColumns("ok", "summary")
    +        .baselineValues(false, String.format(summary, jName))
    +        .go();
    +
    +    fs.delete(renamedBinary, false);
    +    fs.delete(renamedSource, false);
    +    test(String.format("drop function using jar '%s'", jName));
    +  }
    +
    +  @Test
    +  public void testLazyInit() throws Exception {
    +    try {
    +      test("select custom_lower(version) from sys.version");
    +    } catch (UserRemoteException e){
    +      assertThat(e.getMessage(), containsString("No match found for function signature
custom_lower(<CHARACTER>)"));
    +    }
    +
    +    copyJarsToStagingArea();
    +
    +    test(String.format("create function using jar '%s'", jName));
    +    test("select custom_lower(version) from sys.version");
    +
    +    assertTrue("Binary should be present in local UDF area", new File(System.getenv("DRILL_UDF_DIR"),
jName).exists());
    +    assertTrue("Source should be present in local UDF area", new File(System.getenv("DRILL_UDF_DIR"),
sName).exists());
    +    test(String.format("drop function using jar '%s'", jName));
    +  }
    +
    +  @Test
    +  public void testDropFunction() throws Exception {
    --- End diff --
    
    Maybe a test of the trick case in which user A registers a UDF and starts a long-running
query that uses the UDF. User B deregisters that same function. Does the query for user A
continue to run, perhaps using a cached version of the function?
    
    Then, user B registers a new UDF of the same name. Does the registration work? Should
it? If it does, does it create a new copy of the function so that the old and new versions
exist in memory? Now, user B runs a query. It should use the second UDF. But, user A's query
should still be running using the first version.
    
    This will work if the functions are loaded in separate class loaders, and the generate
code is aware of which class loader to use for each function, so that it resolves the correct
one. This, in turn, means that function lookup must be dynamic (by name, based on class loader),
not implicit (which uses a single class loader tree.)
    
    Such a test case might 1) prove the code is rock solid, or 2) suggest ways of enhancing
the code, or 3) point out inherient limitations in the design that we must document (if the
above use case causes failures and we accept those failures as expected behavior.)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

Mime
View raw message