diff --git a/bin/interpreter.sh b/bin/interpreter.sh
index 458ffc00d4..f23ca823e6 100755
--- a/bin/interpreter.sh
+++ b/bin/interpreter.sh
@@ -220,8 +220,8 @@ if [[ ! -z "$ZEPPELIN_IMPERSONATE_USER" ]] && [[ -n "${suid}" || -z "${SPARK_SUB
fi
eval $INTERPRETER_RUN_COMMAND &
-
pid=$!
+
if [[ -z "${pid}" ]]; then
exit 1;
else
diff --git a/bin/stop-interpreter.sh b/bin/stop-interpreter.sh
new file mode 100755
index 0000000000..e6ff16e9e9
--- /dev/null
+++ b/bin/stop-interpreter.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# 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.
+#
+# Stop Zeppelin Interpreter Processes
+#
+
+bin=$(dirname "${BASH_SOURCE-$0}")
+bin=$(cd "${bin}">/dev/null; pwd)
+
+. "${bin}/common.sh"
+
+export ZEPPELIN_FORCE_STOP=1
+
+ZEPPELIN_STOP_INTERPRETER_MAIN=org.apache.zeppelin.interpreter.recovery.StopInterpreter
+ZEPPELIN_LOGFILE="${ZEPPELIN_LOG_DIR}/stop-interpreter.log"
+JAVA_OPTS+=" -Dzeppelin.log.file=${ZEPPELIN_LOGFILE}"
+
+if [[ -d "${ZEPPELIN_HOME}/zeppelin-zengine/target/classes" ]]; then
+ ZEPPELIN_CLASSPATH+=":${ZEPPELIN_HOME}/zeppelin-zengine/target/classes"
+fi
+
+if [[ -d "${ZEPPELIN_HOME}/zeppelin-interpreter/target/classes" ]]; then
+ ZEPPELIN_CLASSPATH+=":${ZEPPELIN_HOME}/zeppelin-interpreter/target/classes"
+fi
+
+addJarInDir "${ZEPPELIN_HOME}/zeppelin-interpreter/target/lib"
+addJarInDir "${ZEPPELIN_HOME}/zeppelin-server/target/lib"
+addJarInDir "${ZEPPELIN_HOME}/lib"
+addJarInDir "${ZEPPELIN_HOME}/lib/interpreter"
+
+CLASSPATH+=":${ZEPPELIN_CLASSPATH}"
+$ZEPPELIN_RUNNER $JAVA_OPTS -cp $CLASSPATH $ZEPPELIN_STOP_INTERPRETER_MAIN ${@}
diff --git a/bin/zeppelin-daemon.sh b/bin/zeppelin-daemon.sh
index 5982aee2e0..e898849751 100755
--- a/bin/zeppelin-daemon.sh
+++ b/bin/zeppelin-daemon.sh
@@ -217,18 +217,6 @@ function stop() {
action_msg "${ZEPPELIN_NAME} stop" "${SET_OK}"
fi
fi
-
- # list all pid that used in remote interpreter and kill them
- for f in ${ZEPPELIN_PID_DIR}/*.pid; do
- if [[ ! -f ${f} ]]; then
- continue;
- fi
-
- pid=$(cat ${f})
- wait_for_zeppelin_to_die $pid 20
- $(rm -f ${f})
- done
-
}
function find_zeppelin_process() {
diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index 1f5c1e5c98..0ad74c446d 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -481,6 +481,45 @@
-->
+
+
+
+
+
+
+
+
+
+
-
diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html
index bccb5b4691..95d83ea27c 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -44,7 +44,7 @@
diff --git a/docs/assets/themes/zeppelin/img/screenshots/conf_interpreter.png b/docs/assets/themes/zeppelin/img/screenshots/conf_interpreter.png
new file mode 100644
index 0000000000..156c3575c9
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/screenshots/conf_interpreter.png differ
diff --git a/docs/index.md b/docs/index.md
index 8f3b551c6a..587ae93ed5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -59,6 +59,7 @@ limitations under the License.
* [Text Display (`%text`)](./usage/display_system/basic.html#text)
* [HTML Display (`%html`)](./usage/display_system/basic.html#html)
* [Table Display (`%table`)](./usage/display_system/basic.html#table)
+ * [Network Display (`%network`)](./usage/display_system/basic.html#network)
* [Angular Display using Backend API (`%angular`)](./usage/display_system/angular_backend.html)
* [Angular Display using Frontend API (`%angular`)](./usage/display_system/angular_frontend.html)
* Interpreter
diff --git a/docs/usage/interpreter/overview.md b/docs/usage/interpreter/overview.md
index ee0c4d796c..035c381b8a 100644
--- a/docs/usage/interpreter/overview.md
+++ b/docs/usage/interpreter/overview.md
@@ -131,3 +131,24 @@ Before 0.8.0, Zeppelin don't have lifecycle management on interpreter. User have
`NullLifecycleManager` will do nothing,
user need to control the lifecycle of interpreter by themselves as before. `TimeoutLifecycleManager` will shutdown interpreters after interpreter idle for a while. By default, the idle threshold is 1 hour.
User can change it via `zeppelin.interpreter.lifecyclemanager.timeout.threshold`. `TimeoutLifecycleManager` is the default lifecycle manager, user can change it via `zeppelin.interpreter.lifecyclemanager.class`.
+
+
+## Generic ConfInterpreter
+
+Zeppelin's interpreter setting is shared by all users and notes, if you want to have different setting you have to create new interpreter, e.g. you can create `spark_jar1` for running spark with dependency jar1 and `spark_jar2` for running spark with dependency jar2.
+This approach works, but not so convenient. `ConfInterpreter` can provide more fine-grained control on interpreter setting and more flexibility.
+
+`ConfInterpreter` is a generic interpreter that could be used by any interpreters. The input format should be property file format.
+It can be used to make custom setting for any interpreter. But it requires to run before interpreter process launched. And when interpreter process is launched is determined by interpreter mode setting.
+So users needs to understand the ([interpreter mode setting ](../usage/interpreter/interpreter_bindings_mode.html) of Zeppelin and be aware when interpreter process is launched. E.g. If we set spark interpreter setting as isolated per note. Under this setting, each note will launch one interpreter process.
+In this scenario, user need to put `ConfInterpreter` as the first paragraph as the below example. Otherwise the customized setting can not be applied (Actually it would report ERROR)
+
+
+
+## Interpreter Process Recovery
+
+Before 0.8.0, shutting down Zeppelin also mean to shutdown all the running interpreter processes. Usually admin will shutdown Zeppelin server for maintenance or upgrade, but don't want to shut down the running interpreter processes.
+In such cases, interpreter process recovery is necessary. Starting from 0.8.0, user can enable interpreter process recovering via setting `zeppelin.recovery.storage.class` as
+`org.apache.zeppelin.interpreter.recovery.FileSystemRecoveryStorage` or other implementations if available in future, by default it is `org.apache.zeppelin.interpreter.recovery.NullRecoveryStorage`
+ which means recovery is not enabled. Enable recover means shutting down Zeppelin would not terminating interpreter process,
+and when Zeppelin is restarted, it would try to reconnect to the existing running interpreter processes. If you want to kill all the interpreter processes after terminating Zeppelin even when recovery is enabled, you can run `bin/stop-interpreter.sh`
diff --git a/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java
new file mode 100644
index 0000000000..00439e8c62
--- /dev/null
+++ b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java
@@ -0,0 +1,63 @@
+/*
+ * 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.zeppelin.kylin;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import org.apache.zeppelin.common.JsonSerializable;
+
+/**
+ * class for Kylin Error Response.
+ */
+class KylinErrorResponse implements JsonSerializable {
+ private static final Gson gson = new Gson();
+
+ private String stacktrace;
+ private String exception;
+ private String url;
+ private String code;
+ private Object data;
+ private String msg;
+
+ public KylinErrorResponse(String stacktrace, String exception, String url,
+ String code, Object data, String msg) {
+ this.stacktrace = stacktrace;
+ this.exception = exception;
+ this.url = url;
+ this.code = code;
+ this.data = data;
+ this.msg = msg;
+ }
+
+ public String getException() {
+ return exception;
+ }
+
+ public String toJson() {
+ return gson.toJson(this);
+ }
+
+ public static KylinErrorResponse fromJson(String json) {
+ try {
+ return gson.fromJson(json, KylinErrorResponse.class);
+ } catch (JsonSyntaxException ex) {
+ return null;
+ }
+ }
+
+}
diff --git a/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
index 6b68d288e4..c7cd689a74 100755
--- a/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
+++ b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
@@ -18,6 +18,7 @@
package org.apache.zeppelin.kylin;
import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
@@ -30,9 +31,7 @@ import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
@@ -166,28 +165,42 @@ public class KylinInterpreter extends Interpreter {
}
private InterpreterResult executeQuery(String sql) throws IOException {
-
HttpResponse response = prepareRequest(sql);
+ String result;
- if (response.getStatusLine().getStatusCode() != 200) {
- logger.error("failed to execute query: " + response.getEntity().getContent().toString());
- return new InterpreterResult(InterpreterResult.Code.ERROR,
- "Failed : HTTP error code " + response.getStatusLine().getStatusCode());
+ try {
+ int code = response.getStatusLine().getStatusCode();
+ result = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+
+ if (code != 200) {
+ StringBuilder errorMessage = new StringBuilder("Failed : HTTP error code " + code + " .");
+ logger.error("Failed to execute query: " + result);
+
+ KylinErrorResponse kylinErrorResponse = KylinErrorResponse.fromJson(result);
+ if (kylinErrorResponse == null) {
+ logger.error("Cannot get json from string: " + result);
+ // when code is 401, the response is html, not json
+ if (code == 401) {
+ errorMessage.append(" Error message: Unauthorized. This request requires "
+ + "HTTP authentication. Please make sure your have set your credentials "
+ + "correctly.");
+ } else {
+ errorMessage.append(" Error message: " + result + " .");
+ }
+ } else {
+ String exception = kylinErrorResponse.getException();
+ logger.error("The exception is " + exception);
+ errorMessage.append(" Error message: " + exception + " .");
+ }
+
+ return new InterpreterResult(InterpreterResult.Code.ERROR, errorMessage.toString());
+ }
+ } catch (NullPointerException | IOException e) {
+ throw new IOException(e);
}
- BufferedReader br = new BufferedReader(
- new InputStreamReader((response.getEntity().getContent())));
- StringBuilder sb = new StringBuilder();
-
- String output;
- logger.info("Output from Server .... \n");
- while ((output = br.readLine()) != null) {
- logger.info(output);
- sb.append(output).append('\n');
- }
- InterpreterResult rett = new InterpreterResult(InterpreterResult.Code.SUCCESS,
- formatResult(sb.toString()));
- return rett;
+ return new InterpreterResult(InterpreterResult.Code.SUCCESS,
+ formatResult(result));
}
String formatResult(String msg) {
@@ -205,16 +218,18 @@ public class KylinInterpreter extends Interpreter {
table = mr.group(1);
}
- String[] row = table.split("],\\[");
- for (int i = 0; i < row.length; i++) {
- String[] col = row[i].split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
- for (int j = 0; j < col.length; j++) {
- if (col[j] != null) {
- col[j] = col[j].replaceAll("^\"|\"$", "");
+ if (table != null && !table.isEmpty()) {
+ String[] row = table.split("],\\[");
+ for (int i = 0; i < row.length; i++) {
+ String[] col = row[i].split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
+ for (int j = 0; j < col.length; j++) {
+ if (col[j] != null) {
+ col[j] = col[j].replaceAll("^\"|\"$", "");
+ }
+ res.append(col[j] + " \t");
}
- res.append(col[j] + " \t");
+ res.append(" \n");
}
- res.append(" \n");
}
return res.toString();
}
diff --git a/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java b/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
index 4471a07689..35f0f3c2eb 100755
--- a/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
+++ b/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
@@ -108,6 +108,30 @@ public class KylinInterpreterTest {
Assert.assertEquals(expected, actual);
}
+ @Test
+ public void testParseEmptyResult() {
+ String msg = "{\"columnMetas\":[{\"isNullable\":1,\"displaySize\":256,\"label\":\"COUNTRY\",\"name\":\"COUNTRY\","
+ + "\"schemaName\":\"DEFAULT\",\"catelogName\":null,\"tableName\":\"SALES_TABLE\",\"precision\":256,"
+ + "\"scale\":0,\"columnType\":12,\"columnTypeName\":\"VARCHAR\",\"writable\":false,\"readOnly\":true,"
+ + "\"definitelyWritable\":false,\"autoIncrement\":false,\"caseSensitive\":true,\"searchable\":false,"
+ + "\"currency\":false,\"signed\":true},{\"isNullable\":1,\"displaySize\":256,\"label\":\"CURRENCY\","
+ + "\"name\":\"CURRENCY\",\"schemaName\":\"DEFAULT\",\"catelogName\":null,\"tableName\":\"SALES_TABLE\","
+ + "\"precision\":256,\"scale\":0,\"columnType\":12,\"columnTypeName\":\"VARCHAR\",\"writable\":false,"
+ + "\"readOnly\":true,\"definitelyWritable\":false,\"autoIncrement\":false,\"caseSensitive\":true,"
+ + "\"searchable\":false,\"currency\":false,\"signed\":true},{\"isNullable\":0,\"displaySize\":19,"
+ + "\"label\":\"COUNT__\",\"name\":\"COUNT__\",\"schemaName\":\"DEFAULT\",\"catelogName\":null,"
+ + "\"tableName\":\"SALES_TABLE\",\"precision\":19,\"scale\":0,\"columnType\":-5,\"columnTypeName\":"
+ + "\"BIGINT\",\"writable\":false,\"readOnly\":true,\"definitelyWritable\":false,\"autoIncrement\":false,"
+ + "\"caseSensitive\":true,\"searchable\":false,\"currency\":false,\"signed\":true}],\"results\":"
+ + "[]," + "\"cube\":\"Sample_Cube\",\"affectedRowCount\":0,\"isException\":false,\"exceptionMessage\":null,"
+ + "\"duration\":134,\"totalScanCount\":1,\"hitExceptionCache\":false,\"storageCacheUsed\":false,"
+ + "\"partial\":false}";
+ String expected="%table COUNTRY \tCURRENCY \tCOUNT__ \t \n";
+ KylinInterpreter t = new MockKylinInterpreter(getDefaultProperties());
+ String actual = t.formatResult(msg);
+ Assert.assertEquals(expected, actual);
+ }
+
private Properties getDefaultProperties() {
Properties prop = new Properties();
prop.put("kylin.api.username", "ADMIN");
diff --git a/spark/src/main/resources/interpreter-setting.json b/spark/src/main/resources/interpreter-setting.json
index 485f6950df..d656532eb0 100644
--- a/spark/src/main/resources/interpreter-setting.json
+++ b/spark/src/main/resources/interpreter-setting.json
@@ -61,7 +61,7 @@
"description": "Spark master uri. ex) spark://masterhost:7077",
"type": "string"
},
- "zeppelin.spark.unSupportedVersionCheck": {
+ "zeppelin.spark.enableSupportedVersionCheck": {
"envName": null,
"propertyName": "zeppelin.spark.enableSupportedVersionCheck",
"defaultValue": true,
diff --git a/testing/install_external_dependencies.sh b/testing/install_external_dependencies.sh
index daa670bc7d..e34296e3ab 100755
--- a/testing/install_external_dependencies.sh
+++ b/testing/install_external_dependencies.sh
@@ -44,6 +44,6 @@ if [[ -n "$PYTHON" ]] ; then
conda update -q conda
conda info -a
conda config --add channels conda-forge
- conda install -q matplotlib pandasql ipython jupyter_client ipykernel matplotlib bokeh=0.12.6
+ conda install -q matplotlib pandasql ipython=5.4.1 jupyter_client ipykernel matplotlib bokeh=0.12.6
pip install -q grpcio ggplot
fi
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index ab120f235f..37fbce1b1f 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -311,7 +311,7 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
The following components are provided under the BSD-style License.
- (New BSD License) JGit (org.eclipse.jgit:org.eclipse.jgit:jar:4.1.1.201511131810-r - https://eclipse.org/jgit/)
+ (New BSD License) JGit (org.eclipse.jgit:org.eclipse.jgit:jar:4.5.4.201711221230-r - https://eclipse.org/jgit/)
(New BSD License) Kryo (com.esotericsoftware.kryo:kryo:3.0.3 - http://code.google.com/p/kryo/)
(New BSD License) MinLog (com.esotericsoftware.minlog:minlog:1.3 - http://code.google.com/p/minlog/)
(New BSD License) ReflectASM (com.esotericsoftware.reflectasm:reflectasm:1.07 - http://code.google.com/p/reflectasm/)
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 508ed8398b..992e90f7ed 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -355,6 +355,19 @@ public class ZeppelinConfiguration extends XMLConfiguration {
return getString(ConfVars.ZEPPELIN_NOTEBOOK_DIR);
}
+ public String getRecoveryDir() {
+ return getRelativeDir(ConfVars.ZEPPELIN_RECOVERY_DIR);
+ }
+
+ public String getRecoveryStorageClass() {
+ return getString(ConfVars.ZEPPELIN_RECOVERY_STORAGE_CLASS);
+ }
+
+ public boolean isRecoveryEnabled() {
+ return !getString(ConfVars.ZEPPELIN_RECOVERY_STORAGE_CLASS).equals(
+ "org.apache.zeppelin.interpreter.recovery.NullRecoveryStorage");
+ }
+
public String getUser() {
return getString(ConfVars.ZEPPELIN_NOTEBOOK_S3_USER);
}
@@ -674,6 +687,10 @@ public class ZeppelinConfiguration extends XMLConfiguration {
ZEPPELIN_INTERPRETER_OUTPUT_LIMIT("zeppelin.interpreter.output.limit", 1024 * 100),
ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"),
ZEPPELIN_NOTEBOOK_DIR("zeppelin.notebook.dir", "notebook"),
+ ZEPPELIN_RECOVERY_DIR("zeppelin.recovery.dir", "recovery"),
+ ZEPPELIN_RECOVERY_STORAGE_CLASS("zeppelin.recovery.storage.class",
+ "org.apache.zeppelin.interpreter.recovery.NullRecoveryStorage"),
+
// use specified notebook (id) as homescreen
ZEPPELIN_NOTEBOOK_HOMESCREEN("zeppelin.notebook.homescreen", null),
// whether homescreen notebook will be hidden from notebook list or not
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterClient.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterClient.java
index b991079fec..813dad8688 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterClient.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterClient.java
@@ -19,8 +19,20 @@ package org.apache.zeppelin.interpreter.launcher;
/**
* Interface to InterpreterClient which is created by InterpreterLauncher. This is the component
- * that is used to for the communication fromzeppelin-server process to zeppelin interpreter process
+ * that is used to for the communication from zeppelin-server process to zeppelin interpreter
+ * process.
*/
public interface InterpreterClient {
+ String getInterpreterSettingName();
+
+ void start(String userName, Boolean isUserImpersonate);
+
+ void stop();
+
+ String getHost();
+
+ int getPort();
+
+ boolean isRunning();
}
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLaunchContext.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLaunchContext.java
index 9e253555a9..6901e2c7a6 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLaunchContext.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLaunchContext.java
@@ -30,6 +30,7 @@ public class InterpreterLaunchContext {
private Properties properties;
private InterpreterOption option;
private InterpreterRunner runner;
+ private String interpreterGroupId;
private String interpreterSettingId;
private String interpreterSettingGroup;
private String interpreterSettingName;
@@ -37,12 +38,14 @@ public class InterpreterLaunchContext {
public InterpreterLaunchContext(Properties properties,
InterpreterOption option,
InterpreterRunner runner,
+ String interpreterGroupId,
String interpreterSettingId,
String interpreterSettingGroup,
String interpreterSettingName) {
this.properties = properties;
this.option = option;
this.runner = runner;
+ this.interpreterGroupId = interpreterGroupId;
this.interpreterSettingId = interpreterSettingId;
this.interpreterSettingGroup = interpreterSettingGroup;
this.interpreterSettingName = interpreterSettingName;
@@ -60,6 +63,10 @@ public class InterpreterLaunchContext {
return runner;
}
+ public String getInterpreterGroupId() {
+ return interpreterGroupId;
+ }
+
public String getInterpreterSettingId() {
return interpreterSettingId;
}
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLauncher.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLauncher.java
index 5d0acf3515..1cee20e7a0 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLauncher.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/launcher/InterpreterLauncher.java
@@ -18,6 +18,7 @@
package org.apache.zeppelin.interpreter.launcher;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.interpreter.recovery.RecoveryStorage;
import java.io.IOException;
import java.util.Properties;
@@ -29,9 +30,11 @@ public abstract class InterpreterLauncher {
protected ZeppelinConfiguration zConf;
protected Properties properties;
+ protected RecoveryStorage recoveryStorage;
- public InterpreterLauncher(ZeppelinConfiguration zConf) {
+ public InterpreterLauncher(ZeppelinConfiguration zConf, RecoveryStorage recoveryStorage) {
this.zConf = zConf;
+ this.recoveryStorage = recoveryStorage;
}
public abstract InterpreterClient launch(InterpreterLaunchContext context) throws IOException;
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/recovery/RecoveryStorage.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/recovery/RecoveryStorage.java
new file mode 100644
index 0000000000..8bbe8302fc
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/recovery/RecoveryStorage.java
@@ -0,0 +1,80 @@
+/*
+ * 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.zeppelin.interpreter.recovery;
+
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.interpreter.launcher.InterpreterClient;
+
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * Interface for storing interpreter process recovery metadata.
+ *
+ */
+public abstract class RecoveryStorage {
+
+ protected ZeppelinConfiguration zConf;
+ protected Map restoredClients;
+
+ public RecoveryStorage(ZeppelinConfiguration zConf) throws IOException {
+ this.zConf = zConf;
+ }
+
+ /**
+ * Update RecoveryStorage when new InterpreterClient is started
+ * @param client
+ * @throws IOException
+ */
+ public abstract void onInterpreterClientStart(InterpreterClient client) throws IOException;
+
+ /**
+ * Update RecoveryStorage when InterpreterClient is stopped
+ * @param client
+ * @throws IOException
+ */
+ public abstract void onInterpreterClientStop(InterpreterClient client) throws IOException;
+
+ /**
+ *
+ * It is only called when Zeppelin Server is started.
+ *
+ * @return
+ * @throws IOException
+ */
+ public abstract Map restore() throws IOException;
+
+
+ /**
+ * It is called after constructor
+ *
+ * @throws IOException
+ */
+ public void init() throws IOException {
+ this.restoredClients = restore();
+ }
+
+ public InterpreterClient getInterpreterClient(String interpreterGroupId) {
+ if (restoredClients.containsKey(interpreterGroupId)) {
+ return restoredClients.get(interpreterGroupId);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/zeppelin-server/notebook/.python.recovery.crc b/zeppelin-server/notebook/.python.recovery.crc
new file mode 100644
index 0000000000..6bd3e7ae43
Binary files /dev/null and b/zeppelin-server/notebook/.python.recovery.crc differ
diff --git a/zeppelin-server/notebook/python.recovery b/zeppelin-server/notebook/python.recovery
new file mode 100644
index 0000000000..eaf4938fda
--- /dev/null
+++ b/zeppelin-server/notebook/python.recovery
@@ -0,0 +1 @@
+2CZA1DVUG:shared_process 192.168.3.2:55410
\ No newline at end of file
diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml
index 08ede293e4..925c637fcf 100644
--- a/zeppelin-server/pom.xml
+++ b/zeppelin-server/pom.xml
@@ -349,6 +349,21 @@
+
+ maven-surefire-plugin
+ ${plugin.surefire.version}
+
+ -Xmx2g -Xms1g -Dfile.encoding=UTF-8
+
+ ${tests.to.exclude}
+
+
+ 1
+
+
+
+
+
org.scala-toolsmaven-scala-plugin
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index 0b66a437d5..f8625c2357 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -162,7 +162,7 @@ public class ZeppelinServer extends Application {
public static void main(String[] args) throws InterruptedException {
- ZeppelinConfiguration conf = ZeppelinConfiguration.create();
+ final ZeppelinConfiguration conf = ZeppelinConfiguration.create();
conf.setProperty("args", args);
jettyWebServer = setupJettyServer(conf);
@@ -199,7 +199,9 @@ public class ZeppelinServer extends Application {
LOG.info("Shutting down Zeppelin Server ... ");
try {
jettyWebServer.stop();
- notebook.getInterpreterSettingManager().close();
+ if (!conf.isRecoveryEnabled()) {
+ ZeppelinServer.notebook.getInterpreterSettingManager().close();
+ }
notebook.close();
Thread.sleep(3000);
} catch (Exception e) {
@@ -222,7 +224,9 @@ public class ZeppelinServer extends Application {
}
jettyWebServer.join();
- ZeppelinServer.notebook.getInterpreterSettingManager().close();
+ if (!conf.isRecoveryEnabled()) {
+ ZeppelinServer.notebook.getInterpreterSettingManager().close();
+ }
}
private static Server setupJettyServer(ZeppelinConfiguration conf) {
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java
new file mode 100644
index 0000000000..37277ee0c3
--- /dev/null
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.zeppelin.recovery;
+
+import com.google.common.io.Files;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.io.FileUtils;
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.interpreter.ManagedInterpreterGroup;
+import org.apache.zeppelin.interpreter.recovery.FileSystemRecoveryStorage;
+import org.apache.zeppelin.interpreter.recovery.StopInterpreter;
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.rest.AbstractTestRestApi;
+import org.apache.zeppelin.scheduler.Job;
+import org.apache.zeppelin.server.ZeppelinServer;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class RecoveryTest extends AbstractTestRestApi {
+
+ private Gson gson = new Gson();
+ private static File recoveryDir = null;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_RECOVERY_STORAGE_CLASS.getVarName(),
+ FileSystemRecoveryStorage.class.getName());
+ recoveryDir = Files.createTempDir();
+ System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_RECOVERY_DIR.getVarName(), recoveryDir.getAbsolutePath());
+ startUp(RecoveryTest.class.getSimpleName());
+ }
+
+ @AfterClass
+ public static void destroy() throws Exception {
+ shutDown();
+ FileUtils.deleteDirectory(recoveryDir);
+ }
+
+ @Test
+ public void testRecovery() throws Exception {
+ Note note1 = ZeppelinServer.notebook.createNote(AuthenticationInfo.ANONYMOUS);
+
+ // run python interpreter and create new variable `user`
+ Paragraph p1 = note1.addNewParagraph(AuthenticationInfo.ANONYMOUS);
+ p1.setText("%python user='abc'");
+ PostMethod post = httpPost("/notebook/job/" + note1.getId(), "");
+ assertThat(post, isAllowed());
+ Map resp = gson.fromJson(post.getResponseBodyAsString(), new TypeToken