/* * 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 * * 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 gobblin.compliance; import java.io.Closeable; import java.io.IOException; import java.security.PrivilegedExceptionAction; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.hadoop.hive.metastore.api.MetaException; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hive.jdbc.HiveConnection; import org.apache.thrift.TException; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import edu.umd.cs.findbugs.annotations.SuppressWarnings; import lombok.extern.slf4j.Slf4j; import gobblin.configuration.ConfigurationKeys; import gobblin.configuration.State; import gobblin.util.HostUtils; /** * This class is responsible for executing Hive queries using jdbc connection. * This class can execute queries as hadoop proxy users by first authenticating hadoop super user. * * @author adsharma */ @Slf4j @SuppressWarnings public class HiveProxyQueryExecutor implements QueryExecutor, Closeable { private static final String DEFAULT = "default"; private static final Splitter SC_SPLITTER = Splitter.on(";").omitEmptyStrings().trimResults(); private Map<String, HiveConnection> connectionMap = new HashMap<>(); private Map<String, Statement> statementMap = new HashMap<>(); private State state; private List<String> settings = new ArrayList<>(); /** * Instantiates a new Hive proxy query executor. * * @param state the state * @param proxies the proxies * @throws IOException the io exception */ public HiveProxyQueryExecutor(State state, List<String> proxies) throws IOException { try { this.state = new State(state); setHiveSettings(state); if (proxies.isEmpty()) { setConnection(); } else { setProxiedConnection(proxies); } } catch (InterruptedException | TException | ClassNotFoundException | SQLException e) { throw new IOException(e); } } /** * Instantiates a new Hive proxy query executor. * * @param state the state * @throws IOException the io exception */ public HiveProxyQueryExecutor(State state) throws IOException { this(state, getProxiesFromState(state)); } private static List<String> getProxiesFromState(State state) { if (!state.getPropAsBoolean(ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_SHOULD_PROXY, ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_DEFAULT_SHOULD_PROXY)) { return Collections.emptyList(); } Preconditions.checkArgument(state.contains(ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_PROXY_USER), "Missing required property " + ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_PROXY_USER); Preconditions.checkArgument(state.contains(ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_SUPER_USER), "Missing required property " + ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_SUPER_USER); List<String> proxies = new ArrayList<>(); proxies.add(state.getProp(ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_PROXY_USER)); proxies.add(state.getProp(ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_SUPER_USER)); return proxies; } private synchronized void setProxiedConnection(final List<String> proxies) throws IOException, InterruptedException, TException { Preconditions.checkArgument(this.state.contains(ConfigurationKeys.SUPER_USER_KEY_TAB_LOCATION), "Missing required property " + ConfigurationKeys.SUPER_USER_KEY_TAB_LOCATION); String superUser = this.state.getProp(ComplianceConfigurationKeys.GOBBLIN_COMPLIANCE_SUPER_USER); String keytabLocation = this.state.getProp(ConfigurationKeys.SUPER_USER_KEY_TAB_LOCATION); String realm = this.state.getProp(ConfigurationKeys.KERBEROS_REALM); UserGroupInformation loginUser = UserGroupInformation .loginUserFromKeytabAndReturnUGI(HostUtils.getPrincipalUsingHostname(superUser, realm), keytabLocation); loginUser.doAs(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws MetaException, SQLException, ClassNotFoundException { for (String proxy : proxies) { HiveConnection hiveConnection = getHiveConnection(Optional.fromNullable(proxy)); Statement statement = hiveConnection.createStatement(); statementMap.put(proxy, statement); connectionMap.put(proxy, hiveConnection); for (String setting : settings) { statement.execute(setting); } } return null; } }); } private synchronized void setConnection() throws ClassNotFoundException, SQLException { HiveConnection hiveConnection = getHiveConnection(Optional.<String>absent()); Statement statement = hiveConnection.createStatement(); this.statementMap.put(DEFAULT, statement); this.connectionMap.put(DEFAULT, hiveConnection); for (String setting : settings) { statement.execute(setting); } } private HiveConnection getHiveConnection(Optional<String> proxyUser) throws ClassNotFoundException, SQLException { Class.forName("org.apache.hive.jdbc.HiveDriver"); Preconditions.checkArgument(this.state.contains(ComplianceConfigurationKeys.HIVE_JDBC_URL), "Missing required property " + ComplianceConfigurationKeys.HIVE_JDBC_URL); String url = this.state.getProp(ComplianceConfigurationKeys.HIVE_JDBC_URL); if (proxyUser.isPresent()) { url = url + ComplianceConfigurationKeys.HIVE_SERVER2_PROXY_USER + proxyUser.get(); } return (HiveConnection) DriverManager.getConnection(url); } @Override public void executeQueries(List<String> queries) throws SQLException { executeQueries(queries, Optional.<String>absent()); } @Override public void executeQuery(String query) throws SQLException { executeQuery(query, Optional.<String>absent()); } /** * Execute queries. * * @param queries the queries * @param proxy the proxy * @throws SQLException the sql exception */ public void executeQueries(List<String> queries, Optional<String> proxy) throws SQLException { Preconditions.checkArgument(!this.statementMap.isEmpty(), "No hive connection. Unable to execute queries"); if (!proxy.isPresent()) { Preconditions.checkArgument(this.statementMap.size() == 1, "Multiple Hive connections. Please specify a user"); proxy = Optional.fromNullable(this.statementMap.keySet().iterator().next()); } Statement statement = this.statementMap.get(proxy.get()); for (String query : queries) { statement.execute(query); } } /** * Execute query. * * @param query the query * @param proxy the proxy * @throws SQLException the sql exception */ public void executeQuery(String query, Optional<String> proxy) throws SQLException { executeQueries(Collections.singletonList(query), proxy); } @Override public void close() throws IOException { try { for (Map.Entry<String, Statement> entry : this.statementMap.entrySet()) { if (entry.getValue() != null) { entry.getValue().close(); } } for (Map.Entry<String, HiveConnection> entry : this.connectionMap.entrySet()) { if (entry.getValue() != null) { entry.getValue().close(); } } } catch (SQLException e) { throw new IOException(e); } } private void setHiveSettings(State state) { if (state.contains(ComplianceConfigurationKeys.HIVE_SETTINGS)) { String queryString = state.getProp(ComplianceConfigurationKeys.HIVE_SETTINGS); this.settings = SC_SPLITTER.splitToList(queryString); } } }