/**
* 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 org.apache.hadoop.hive.metastore;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hadoop.hive.common.classification.InterfaceAudience;
import org.apache.hadoop.hive.common.classification.InterfaceStability;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.metastore.api.MetaException;
import org.apache.hadoop.hive.metastore.hooks.JDOConnectionURLHook;
import org.apache.hadoop.util.ReflectionUtils;
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class RetryingRawStore implements InvocationHandler {
private static final Log LOG = LogFactory.getLog(RetryingRawStore.class);
private final RawStore base;
private int retryInterval = 0;
private int retryLimit = 0;
private JDOConnectionURLHook urlHook = null;
private String urlHookClassName = "";
private final int id;
private final HiveConf hiveConf;
private final Configuration conf; // thread local conf from HMS
protected RetryingRawStore(HiveConf hiveConf, Configuration conf,
Class<? extends RawStore> rawStoreClass, int id) throws MetaException {
this.conf = conf;
this.hiveConf = hiveConf;
this.id = id;
// This has to be called before initializing the instance of RawStore
init();
this.base = (RawStore) ReflectionUtils.newInstance(rawStoreClass, conf);
}
public static RawStore getProxy(HiveConf hiveConf, Configuration conf, String rawStoreClassName,
int id) throws MetaException {
Class<? extends RawStore> baseClass = (Class<? extends RawStore>) MetaStoreUtils.getClass(
rawStoreClassName);
RetryingRawStore handler = new RetryingRawStore(hiveConf, conf, baseClass, id);
return (RawStore) Proxy.newProxyInstance(RetryingRawStore.class.getClassLoader()
, baseClass.getInterfaces(), handler);
}
private void init() throws MetaException {
retryInterval = HiveConf.getIntVar(hiveConf,
HiveConf.ConfVars.METASTOREINTERVAL);
retryLimit = HiveConf.getIntVar(hiveConf,
HiveConf.ConfVars.METASTOREATTEMPTS);
// Using the hook on startup ensures that the hook always has priority
// over settings in *.xml. The thread local conf needs to be used because at this point
// it has already been initialized using hiveConf.
updateConnectionURL(getConf(), null);
}
private void initMS() {
base.setConf(getConf());
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
boolean gotNewConnectUrl = false;
boolean reloadConf = HiveConf.getBoolVar(hiveConf,
HiveConf.ConfVars.METASTOREFORCERELOADCONF);
if (reloadConf) {
updateConnectionURL(getConf(), null);
}
int retryCount = 0;
Exception caughtException = null;
while (true) {
try {
if (reloadConf || gotNewConnectUrl) {
initMS();
}
ret = method.invoke(base, args);
break;
} catch (javax.jdo.JDOException e) {
caughtException = e;
} catch (UndeclaredThrowableException e) {
throw e.getCause();
} catch (InvocationTargetException e) {
throw e.getCause();
}
if (retryCount >= retryLimit) {
throw caughtException;
}
assert (retryInterval >= 0);
retryCount++;
LOG.error(
String.format(
"JDO datastore error. Retrying metastore command " +
"after %d ms (attempt %d of %d)", retryInterval, retryCount, retryLimit));
Thread.sleep(retryInterval);
// If we have a connection error, the JDO connection URL hook might
// provide us with a new URL to access the datastore.
String lastUrl = getConnectionURL(getConf());
gotNewConnectUrl = updateConnectionURL(getConf(), lastUrl);
}
return ret;
}
/**
* Updates the connection URL in hiveConf using the hook
*
* @return true if a new connection URL was loaded into the thread local
* configuration
*/
private boolean updateConnectionURL(Configuration conf, String badUrl)
throws MetaException {
String connectUrl = null;
String currentUrl = getConnectionURL(conf);
try {
// We always call init because the hook name in the configuration could
// have changed.
initConnectionUrlHook();
if (urlHook != null) {
if (badUrl != null) {
urlHook.notifyBadConnectionUrl(badUrl);
}
connectUrl = urlHook.getJdoConnectionUrl(hiveConf);
}
} catch (Exception e) {
LOG.error("Exception while getting connection URL from the hook: " +
e);
}
if (connectUrl != null && !connectUrl.equals(currentUrl)) {
LOG.error(addPrefix(
String.format("Overriding %s with %s",
HiveConf.ConfVars.METASTORECONNECTURLKEY.toString(),
connectUrl)));
conf.set(HiveConf.ConfVars.METASTORECONNECTURLKEY.toString(),
connectUrl);
return true;
}
return false;
}
private static String getConnectionURL(Configuration conf) {
return conf.get(
HiveConf.ConfVars.METASTORECONNECTURLKEY.toString(), "");
}
// Multiple threads could try to initialize at the same time.
synchronized private void initConnectionUrlHook()
throws ClassNotFoundException {
String className =
hiveConf.get(HiveConf.ConfVars.METASTORECONNECTURLHOOK.toString(), "").trim();
if (className.equals("")) {
urlHookClassName = "";
urlHook = null;
return;
}
boolean urlHookChanged = !urlHookClassName.equals(className);
if (urlHook == null || urlHookChanged) {
urlHookClassName = className.trim();
Class<?> urlHookClass = Class.forName(urlHookClassName, true,
JavaUtils.getClassLoader());
urlHook = (JDOConnectionURLHook) ReflectionUtils.newInstance(urlHookClass, null);
}
return;
}
private String addPrefix(String s) {
return id + ": " + s;
}
public Configuration getConf() {
return conf;
}
}