/* * 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.exec.planner.sql.handlers; import com.google.common.collect.Lists; import org.apache.calcite.sql.SqlCharStringLiteral; import org.apache.calcite.sql.SqlNode; import org.apache.drill.common.exceptions.DrillRuntimeException; import org.apache.drill.common.exceptions.UserException; import org.apache.drill.exec.ExecConstants; import org.apache.drill.exec.exception.VersionMismatchException; import org.apache.drill.exec.expr.fn.registry.RemoteFunctionRegistry; import org.apache.drill.exec.physical.PhysicalPlan; import org.apache.drill.exec.planner.sql.DirectPlan; import org.apache.drill.exec.planner.sql.parser.SqlDropFunction; import org.apache.drill.exec.proto.UserBitShared.Jar; import org.apache.drill.exec.proto.UserBitShared.Registry; import org.apache.drill.exec.store.sys.store.DataChangeVersion; import org.apache.drill.exec.util.JarUtil; import org.apache.drill.exec.work.foreman.ForemanSetupException; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import java.io.IOException; import java.util.List; public class DropFunctionHandler extends DefaultSqlHandler { private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DropFunctionHandler.class); public DropFunctionHandler(SqlHandlerConfig config) { super(config); } /** * Unregisters UDFs dynamically. Process consists of several steps: * <ol> * <li>Registering jar in jar registry to ensure that several jars with the same name is not being unregistered.</li> * <li>Starts remote unregistration process, gets list of all jars and excludes jar to be deleted.</li> * <li>Signals drill bits to start local unregistration process.</li> * <li>Removes source and binary jars from registry area.</li> * </ol> * * UDFs unregistration is allowed only if dynamic UDFs support is enabled. * Only jars registered dynamically can be unregistered, * built-in functions loaded at start up are not allowed to be unregistered. * * Limitation: before jar unregistration make sure no one is using functions from this jar. * There is no guarantee that running queries will finish successfully or give correct result. * * @return - Single row indicating list of unregistered UDFs, raise exception otherwise */ @Override public PhysicalPlan getPlan(SqlNode sqlNode) throws ForemanSetupException, IOException { if (!context.getOption(ExecConstants.DYNAMIC_UDF_SUPPORT_ENABLED).bool_val) { throw UserException.validationError() .message("Dynamic UDFs support is disabled.") .build(logger); } SqlDropFunction node = unwrap(sqlNode, SqlDropFunction.class); String jarName = ((SqlCharStringLiteral) node.getJar()).toValue(); RemoteFunctionRegistry remoteFunctionRegistry = context.getRemoteFunctionRegistry(); boolean inProgress = false; try { final String action = remoteFunctionRegistry.addToJars(jarName, RemoteFunctionRegistry.Action.UNREGISTRATION); if (!(inProgress = action == null)) { return DirectPlan.createDirectPlan(context, false, String.format("Jar with %s name is used. Action: %s", jarName, action)); } Jar deletedJar = unregister(jarName, remoteFunctionRegistry); if (deletedJar == null) { return DirectPlan.createDirectPlan(context, false, String.format("Jar %s is not registered in remote registry", jarName)); } remoteFunctionRegistry.submitForUnregistration(jarName); removeJarFromArea(jarName, remoteFunctionRegistry.getFs(), remoteFunctionRegistry.getRegistryArea()); removeJarFromArea(JarUtil.getSourceName(jarName), remoteFunctionRegistry.getFs(), remoteFunctionRegistry.getRegistryArea()); return DirectPlan.createDirectPlan(context, true, String.format("The following UDFs in jar %s have been unregistered:\n%s", jarName, deletedJar.getFunctionSignatureList())); } catch (Exception e) { logger.error("Error during UDF unregistration", e); return DirectPlan.createDirectPlan(context, false, e.getMessage()); } finally { if (inProgress) { remoteFunctionRegistry.finishUnregistration(jarName); remoteFunctionRegistry.removeFromJars(jarName); } } } /** * Gets remote function registry with version. * Version is used to ensure that we update the same registry we removed jars from. * Looks for a jar to be deleted, if founds one, * attempts to update remote registry with list of jars, that excludes jar to be deleted. * If during update {@link VersionMismatchException} was detected, * attempts to repeat unregistration process till retry attempts exceeds the limit. * If retry attempts number hits 0, throws exception that failed to update remote function registry. * * @param jarName jar name * @param remoteFunctionRegistry remote function registry * @return jar that was unregistered, null otherwise */ private Jar unregister(String jarName, RemoteFunctionRegistry remoteFunctionRegistry) { int retryAttempts = remoteFunctionRegistry.getRetryAttempts(); while (retryAttempts >= 0) { DataChangeVersion version = new DataChangeVersion(); Registry registry = remoteFunctionRegistry.getRegistry(version); Jar jarToBeDeleted = null; List<Jar> jars = Lists.newArrayList(); for (Jar j : registry.getJarList()) { if (j.getName().equals(jarName)) { jarToBeDeleted = j; } else { jars.add(j); } } if (jarToBeDeleted == null) { return null; } Registry updatedRegistry = Registry.newBuilder().addAllJar(jars).build(); try { remoteFunctionRegistry.updateRegistry(updatedRegistry, version); return jarToBeDeleted; } catch (VersionMismatchException ex) { logger.debug("Failed to update function registry during unregistration, version mismatch was detected.", ex); retryAttempts--; } } throw new DrillRuntimeException("Failed to update remote function registry. Exceeded retry attempts limit."); } /** * Removes jar from indicated area, in case of error log it and proceeds. * * @param jarName jar name * @param fs file system * @param area path to area */ private void removeJarFromArea(String jarName, FileSystem fs, Path area) { try { fs.delete(new Path(area, jarName), false); } catch (IOException e) { logger.error("Error removing jar {} from area {}", jarName, area.toUri().getPath()); } } }