Merge remote-tracking branch 'origin/master' into livyInterperter

# Conflicts:
#	conf/zeppelin-site.xml.template
This commit is contained in:
Prabhjyot Singh 2016-03-07 16:52:52 +05:30
commit 0fbb74bcf6
101 changed files with 4236 additions and 1641 deletions

24
.github/PULL_REQUEST_TEMPLATE vendored Normal file
View file

@ -0,0 +1,24 @@
### What is this PR for?
A few sentences describing the overall goals of the pull request's commits.
First time? Check out the contributing guide - https://github.com/apache/incubator-zeppelin/blob/master/CONTRIBUTING.md
### What type of PR is it?
[Bug Fix | Improvement | Feature | Documentation | Hot Fix | Refactoring]
### Todos
* [ ] - Task
### What is the Jira issue?
* Open an issue on Jira https://issues.apache.org/jira/browse/ZEPPELIN/
* Put link here, and add [ZEPPELIN-*Jira number*] in PR title, eg. [ZEPPELIN-533]
### How should this be tested?
Outline the steps to test the PR here.
### Screenshots (if appropriate)
### Questions:
* Does the licenses files need update?
* Is there breaking changes for older versions?
* Does this needs documentation?

View file

@ -17,6 +17,7 @@ In order to make the review process easier, please follow this template when mak
```
### What is this PR for?
A few sentences describing the overall goals of the pull request's commits.
First time? Check out the contributing guide - https://github.com/apache/incubator-zeppelin/blob/master/CONTRIBUTING.md
### What type of PR is it?
[Bug Fix | Improvement | Feature | Documentation | Hot Fix | Refactoring]
@ -24,7 +25,9 @@ A few sentences describing the overall goals of the pull request's commits.
### Todos
* [ ] - Task
### Is there a relevant Jira issue?
### What is the Jira issue?
* Open an issue on Jira https://issues.apache.org/jira/browse/ZEPPELIN/
* Put link here, and add [ZEPPELIN-*Jira number*] in PR title, eg. [ZEPPELIN-533]
### How should this be tested?
Outline the steps to test the PR here.
@ -37,12 +40,6 @@ Outline the steps to test the PR here.
* Does this needs documentation?
```
You can also use this small bookmarklet tool to fill your Pull Request fields automatically:
```
javascript:(function() {var e = document.getElementById('pull_request_body');if (e) {e.value += '### What is this PR for?\nA few sentences describing the overall goals of the pull request\'s commits.\n\n### What type of PR is it?\n[Bug Fix | Improvement | Feature | Documentation | Hot Fix | Refactoring]\n\n### Todos\n* [ ] - Task\n\n### Is there a relevant Jira issue?\n\n### How should this be tested?\nOutline the steps to test the PR here.\n\n### Screenshots (if appropriate)\n\n### Questions:\n* Does the licenses files need update?\n* Is there breaking changes for older versions?\n* Does this needs documentation?';}})();
```
## Testing a Pull Request
You can also test and review a particular Pull Request. Here are two useful ways.

View file

@ -27,14 +27,15 @@
</parent>
<groupId>org.apache.zeppelin</groupId>
<artifactId>zeppelin-tachyon</artifactId>
<artifactId>zeppelin-alluxio</artifactId>
<packaging>jar</packaging>
<version>0.6.0-incubating-SNAPSHOT</version>
<name>Zeppelin: Tachyon interpreter</name>
<name>Zeppelin: Alluxio interpreter</name>
<url>http://www.apache.org</url>
<properties>
<tachyon.version>0.8.2</tachyon.version>
<alluxio.version>1.0.0</alluxio.version>
<powermock.version>1.6.1</powermock.version>
</properties>
<dependencies>
<dependency>
@ -50,9 +51,9 @@
</dependency>
<dependency>
<groupId>org.tachyonproject</groupId>
<artifactId>tachyon-shell</artifactId>
<version>${tachyon.version}</version>
<groupId>org.alluxio</groupId>
<artifactId>alluxio-shell</artifactId>
<version>${alluxio.version}</version>
</dependency>
<dependency>
@ -73,23 +74,58 @@
</dependency>
<dependency>
<groupId>org.tachyonproject</groupId>
<artifactId>tachyon-servers</artifactId>
<version>${tachyon.version}</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.tachyonproject</groupId>
<artifactId>tachyon-minicluster</artifactId>
<version>${tachyon.version}</version>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.tachyonproject</groupId>
<artifactId>tachyon-underfs-local</artifactId>
<version>${tachyon.version}</version>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-reflect</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alluxio</groupId>
<artifactId>alluxio-core-server</artifactId>
<version>${alluxio.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alluxio</groupId>
<artifactId>alluxio-minicluster</artifactId>
<version>${alluxio.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alluxio</groupId>
<artifactId>alluxio-underfs-local</artifactId>
<version>${alluxio.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
@ -127,7 +163,7 @@
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/../../interpreter/tachyon</outputDirectory>
<outputDirectory>${project.build.directory}/../../interpreter/alluxio</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
@ -141,7 +177,7 @@
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/../../interpreter/tachyon</outputDirectory>
<outputDirectory>${project.build.directory}/../../interpreter/alluxio</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>

View file

@ -0,0 +1,254 @@
/**
* 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.alluxio;
import java.io.IOException;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
import java.util.*;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alluxio.Configuration;
import alluxio.shell.AlluxioShell;
/**
* Alluxio interpreter for Zeppelin.
*/
public class AlluxioInterpreter extends Interpreter {
Logger logger = LoggerFactory.getLogger(AlluxioInterpreter.class);
protected static final String ALLUXIO_MASTER_HOSTNAME = "alluxio.master.hostname";
protected static final String ALLUXIO_MASTER_PORT = "alluxio.master.port";
private AlluxioShell fs;
private int totalCommands = 0;
private int completedCommands = 0;
private final String alluxioMasterHostname;
private final String alluxioMasterPort;
protected final List<String> keywords = Arrays.asList("cat", "chgrp",
"chmod", "chown", "copyFromLocal", "copyToLocal", "count",
"createLineage", "deleteLineage", "du", "fileInfo", "free",
"getCapacityBytes", "getUsedBytes", "listLineages", "load",
"loadMetadata", "location", "ls", "mkdir", "mount", "mv",
"persist", "pin", "report", "rm", "setTtl", "tail", "touch",
"unmount", "unpin", "unsetTtl");
public AlluxioInterpreter(Properties property) {
super(property);
alluxioMasterHostname = property.getProperty(ALLUXIO_MASTER_HOSTNAME);
alluxioMasterPort = property.getProperty(ALLUXIO_MASTER_PORT);
}
static {
Interpreter.register("alluxio", "alluxio",
AlluxioInterpreter.class.getName(),
new InterpreterPropertyBuilder()
.add(ALLUXIO_MASTER_HOSTNAME, "localhost", "Alluxio master hostname")
.add(ALLUXIO_MASTER_PORT, "19998", "Alluxio master port")
.build());
}
@Override
public void open() {
logger.info("Starting Alluxio shell to connect to " + alluxioMasterHostname +
" on port " + alluxioMasterPort);
System.setProperty(ALLUXIO_MASTER_HOSTNAME, alluxioMasterHostname);
System.setProperty(ALLUXIO_MASTER_PORT, alluxioMasterPort);
fs = new AlluxioShell(new Configuration());
}
@Override
public void close() {
logger.info("Closing Alluxio shell");
try {
fs.close();
} catch (IOException e) {
logger.error("Cannot close connection", e);
}
}
@Override
public InterpreterResult interpret(String st, InterpreterContext context) {
String[] lines = splitAndRemoveEmpty(st, "\n");
return interpret(lines, context);
}
private InterpreterResult interpret(String[] commands, InterpreterContext context) {
boolean isSuccess = true;
totalCommands = commands.length;
completedCommands = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
PrintStream old = System.out;
System.setOut(ps);
for (String command : commands) {
int commandResuld = 1;
String[] args = splitAndRemoveEmpty(command, " ");
if (args.length > 0 && args[0].equals("help")) {
System.out.println(getCommandList());
} else {
commandResuld = fs.run(args);
}
if (commandResuld != 0) {
isSuccess = false;
break;
} else {
completedCommands += 1;
}
System.out.println();
}
System.out.flush();
System.setOut(old);
if (isSuccess) {
return new InterpreterResult(Code.SUCCESS, baos.toString());
} else {
return new InterpreterResult(Code.ERROR, baos.toString());
}
}
private String[] splitAndRemoveEmpty(String st, String splitSeparator) {
String[] voices = st.split(splitSeparator);
ArrayList<String> result = new ArrayList<String>();
for (String voice : voices) {
if (!voice.trim().isEmpty()) {
result.add(voice);
}
}
return result.toArray(new String[result.size()]);
}
private String[] splitAndRemoveEmpty(String[] sts, String splitSeparator) {
ArrayList<String> result = new ArrayList<String>();
for (String st : sts) {
result.addAll(Arrays.asList(splitAndRemoveEmpty(st, splitSeparator)));
}
return result.toArray(new String[result.size()]);
}
@Override
public void cancel(InterpreterContext context) { }
@Override
public FormType getFormType() {
return FormType.NATIVE;
}
@Override
public int getProgress(InterpreterContext context) {
return completedCommands * 100 / totalCommands;
}
@Override
public List<String> completion(String buf, int cursor) {
String[] words = splitAndRemoveEmpty(splitAndRemoveEmpty(buf, "\n"), " ");
String lastWord = "";
if (words.length > 0) {
lastWord = words[ words.length - 1 ];
}
ArrayList<String> voices = new ArrayList<String>();
for (String command : keywords) {
if (command.startsWith(lastWord)) {
voices.add(command);
}
}
return voices;
}
private String getCommandList() {
StringBuilder sb = new StringBuilder();
sb.append("Commands list:");
sb.append("\n\t[help] - List all available commands.");
sb.append("\n\t[cat <path>] - Prints the file's contents to the console.");
sb.append("\n\t[chgrp [-R] <group> <path>] - Changes the group of a file or directory " +
"specified by args. Specify -R to change the group recursively.");
sb.append("\n\t[chmod -R <mode> <path>] - Changes the permission of a file or directory " +
"specified by args. Specify -R to change the permission recursively.");
sb.append("\n\t[chown -R <owner> <path>] - Changes the owner of a file or directory " +
"specified by args. Specify -R to change the owner recursively.");
sb.append("\n\t[copyFromLocal <src> <remoteDst>] - Copies a file or a directory from " +
"local filesystem to Alluxio filesystem.");
sb.append("\n\t[copyToLocal <src> <localDst>] - Copies a file or a directory from the " +
"Alluxio filesystem to the local filesystem.");
sb.append("\n\t[count <path>] - Displays the number of files and directories matching " +
"the specified prefix.");
sb.append("\n\t[createLineage <inputFile1,...> <outputFile1,...> " +
"[<cmd_arg1> <cmd_arg2> ...]] - Creates a lineage.");
sb.append("\n\t[deleteLineage <lineageId> <cascade(true|false)>] - Deletes a lineage. If " +
"cascade is specified as true, dependent lineages will also be deleted.");
sb.append("\n\t[du <path>] - Displays the size of the specified file or directory.");
sb.append("\n\t[fileInfo <path>] - Displays all block info for the specified file.");
sb.append("\n\t[free <file path|folder path>] - Removes the file or directory(recursively) " +
"from Alluxio memory space.");
sb.append("\n\t[getCapacityBytes] - Gets the capacity of the Alluxio file system.");
sb.append("\n\t[getUsedBytes] - Gets number of bytes used in the Alluxio file system.");
sb.append("\n\t[listLineages] - Lists all lineages.");
sb.append("\n\t[load <path>] - Loads a file or directory in Alluxio space, makes it " +
"resident in memory.");
sb.append("\n\t[loadMetadata <path>] - Loads metadata for the given Alluxio path from the " +
"under file system.");
sb.append("\n\t[location <path>] - Displays the list of hosts storing the specified file.");
sb.append("\n\t[ls [-R] <path>] - Displays information for all files and directories " +
"directly under the specified path. Specify -R to display files and " +
"directories recursively.");
sb.append("\n\t[mkdir <path1> [path2] ... [pathn]] - Creates the specified directories, " +
"including any parent directories that are required.");
sb.append("\n\t[mount <alluxioPath> <ufsURI>] - Mounts a UFS path onto an Alluxio path.");
sb.append("\n\t[mv <src> <dst>] - Renames a file or directory.");
sb.append("\n\t[persist <alluxioPath>] - Persists a file or directory currently stored " +
"only in Alluxio to the UnderFileSystem.");
sb.append("\n\t[pin <path>] - Pins the given file or directory in memory (works " +
"recursively for directories). Pinned files are never evicted from memory, unless " +
"TTL is set.");
sb.append("\n\t[report <path>] - Reports to the master that a file is lost.");
sb.append("\n\t[rm [-R] <path>] - Removes the specified file. Specify -R to remove file or " +
"directory recursively.");
sb.append("\n\t[setTtl <path> <time to live(in milliseconds)>] - Sets a new TTL value for " +
"the file at path.");
sb.append("\n\t[tail <path>] - Prints the file's last 1KB of contents to the console.");
sb.append("\n\t[touch <path>] - Creates a 0 byte file. The file will be written to the " +
"under file system.");
sb.append("\n\t[unmount <alluxioPath>] - Unmounts an Alluxio path.");
sb.append("\n\t[unpin <path>] - Unpins the given file or folder from memory " +
"(works recursively for a directory).");
sb.append("\n\\t[unsetTtl <path>] - Unsets the TTL value for the given path.");
sb.append("\n\t[unpin <path>] - Unpin the given file to allow Alluxio to evict " +
"this file again. If the given path is a directory, it recursively unpins " +
"all files contained and any new files created within this directory.");
return sb.toString();
}
}

View file

@ -0,0 +1,463 @@
/**
* 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.alluxio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import alluxio.client.WriteType;
import alluxio.client.file.URIStatus;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.junit.*;
import alluxio.Constants;
import alluxio.AlluxioURI;
import alluxio.client.FileSystemTestUtils;
import alluxio.client.file.FileInStream;
import alluxio.client.file.FileSystem;
import alluxio.exception.ExceptionMessage;
import alluxio.exception.AlluxioException;
import alluxio.master.LocalAlluxioCluster;
import alluxio.shell.command.CommandUtils;
import alluxio.util.FormatUtils;
import alluxio.util.io.BufferUtils;
import alluxio.util.io.PathUtils;
public class AlluxioInterpreterTest {
private AlluxioInterpreter alluxioInterpreter;
private static final int SIZE_BYTES = Constants.MB * 10;
private LocalAlluxioCluster mLocalAlluxioCluster = null;
private FileSystem fs = null;
@After
public final void after() throws Exception {
if (alluxioInterpreter != null) {
alluxioInterpreter.close();
}
mLocalAlluxioCluster.stop();
}
@Before
public final void before() throws Exception {
mLocalAlluxioCluster = new LocalAlluxioCluster(SIZE_BYTES, 1000);
mLocalAlluxioCluster.start();
fs = mLocalAlluxioCluster.getClient();
final Properties props = new Properties();
props.put(AlluxioInterpreter.ALLUXIO_MASTER_HOSTNAME, mLocalAlluxioCluster.getMasterHostname());
props.put(AlluxioInterpreter.ALLUXIO_MASTER_PORT, mLocalAlluxioCluster.getMasterPort() + "");
alluxioInterpreter = new AlluxioInterpreter(props);
alluxioInterpreter.open();
}
@Test
public void testCompletion() {
List<String> expectedResultOne = Arrays.asList("cat", "chgrp",
"chmod", "chown", "copyFromLocal", "copyToLocal", "count",
"createLineage");
List<String> expectedResultTwo = Arrays.asList("copyFromLocal",
"copyToLocal", "count");
List<String> expectedResultThree = Arrays.asList("copyFromLocal", "copyToLocal");
List<String> expectedResultNone = new ArrayList<String>();
List<String> resultOne = alluxioInterpreter.completion("c", 0);
List<String> resultTwo = alluxioInterpreter.completion("co", 0);
List<String> resultThree = alluxioInterpreter.completion("copy", 0);
List<String> resultNotMatch = alluxioInterpreter.completion("notMatch", 0);
List<String> resultAll = alluxioInterpreter.completion("", 0);
Assert.assertEquals(expectedResultOne, resultOne);
Assert.assertEquals(expectedResultTwo, resultTwo);
Assert.assertEquals(expectedResultThree, resultThree);
Assert.assertEquals(expectedResultNone, resultNotMatch);
Assert.assertEquals(alluxioInterpreter.keywords, resultAll);
}
@Test
public void catDirectoryTest() throws IOException {
String expected = "Successfully created directory /testDir\n\n" +
"Path /testDir must be a file\n";
InterpreterResult output = alluxioInterpreter.interpret("mkdir /testDir" +
"\ncat /testDir", null);
Assert.assertEquals(Code.ERROR, output.code());
Assert.assertEquals(expected, output.message());
}
@Test
public void catNotExistTest() throws IOException {
InterpreterResult output = alluxioInterpreter.interpret("cat /testFile", null);
Assert.assertEquals(Code.ERROR, output.code());
}
@Test
public void catTest() throws IOException {
FileSystemTestUtils.createByteFile(fs, "/testFile", WriteType.MUST_CACHE,
10, 10);
InterpreterResult output = alluxioInterpreter.interpret("cat /testFile", null);
byte[] expected = BufferUtils.getIncreasingByteArray(10);
Assert.assertEquals(Code.SUCCESS, output.code());
Assert.assertArrayEquals(expected,
output.message().substring(0, output.message().length() - 1).getBytes());
}
@Test
public void copyFromLocalLargeTest() throws IOException, AlluxioException {
File testFile = new File(mLocalAlluxioCluster.getAlluxioHome() + "/testFile");
testFile.createNewFile();
FileOutputStream fos = new FileOutputStream(testFile);
byte[] toWrite = BufferUtils.getIncreasingByteArray(SIZE_BYTES);
fos.write(toWrite);
fos.close();
InterpreterResult output = alluxioInterpreter.interpret("copyFromLocal " +
testFile.getAbsolutePath() + " /testFile", null);
Assert.assertEquals(
"Copied " + testFile.getAbsolutePath() + " to /testFile\n\n",
output.message());
long fileLength = fs.getStatus(new AlluxioURI("/testFile")).getLength();
Assert.assertEquals(SIZE_BYTES, fileLength);
FileInStream fStream = fs.openFile(new AlluxioURI("/testFile"));
byte[] read = new byte[SIZE_BYTES];
fStream.read(read);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(SIZE_BYTES, read));
}
@Test
public void loadFileTest() throws IOException, AlluxioException {
FileSystemTestUtils.createByteFile(fs, "/testFile", WriteType.CACHE_THROUGH, 10, 10);
int memPercentage = fs.getStatus(new AlluxioURI("/testFile")).getInMemoryPercentage();
Assert.assertFalse(memPercentage == 0);
alluxioInterpreter.interpret("load /testFile", null);
memPercentage = fs.getStatus(new AlluxioURI("/testFile")).getInMemoryPercentage();
Assert.assertTrue(memPercentage == 100);
}
@Test
public void loadDirTest() throws IOException, AlluxioException {
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileA", WriteType.CACHE_THROUGH, 10, 10);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileB", WriteType.MUST_CACHE, 10, 10);
int memPercentageA = fs.getStatus(new AlluxioURI("/testRoot/testFileA")).getInMemoryPercentage();
int memPercentageB = fs.getStatus(new AlluxioURI("/testRoot/testFileB")).getInMemoryPercentage();
Assert.assertFalse(memPercentageA == 0);
Assert.assertTrue(memPercentageB == 100);
alluxioInterpreter.interpret("load /testRoot", null);
memPercentageA = fs.getStatus(new AlluxioURI("/testRoot/testFileA")).getInMemoryPercentage();
memPercentageB = fs.getStatus(new AlluxioURI("/testRoot/testFileB")).getInMemoryPercentage();
Assert.assertTrue(memPercentageA == 100);
Assert.assertTrue(memPercentageB == 100);
}
@Test
public void copyFromLocalTest() throws IOException, AlluxioException {
File testDir = new File(mLocalAlluxioCluster.getAlluxioHome() + "/testDir");
testDir.mkdir();
File testDirInner = new File(mLocalAlluxioCluster.getAlluxioHome() + "/testDir/testDirInner");
testDirInner.mkdir();
File testFile =
generateFileContent("/testDir/testFile", BufferUtils.getIncreasingByteArray(10));
generateFileContent("/testDir/testDirInner/testFile2",
BufferUtils.getIncreasingByteArray(10, 20));
InterpreterResult output = alluxioInterpreter.interpret("copyFromLocal " +
testFile.getParent() + " /testDir", null);
Assert.assertEquals(
"Copied " + testFile.getParent() + " to /testDir\n\n",
output.message());
long fileLength1 = fs.getStatus(new AlluxioURI("/testDir/testFile")).getLength();
long fileLength2 = fs.getStatus(new AlluxioURI("/testDir/testDirInner/testFile2")).getLength();
Assert.assertEquals(10, fileLength1);
Assert.assertEquals(20, fileLength2);
FileInStream fStream1 = fs.openFile(new AlluxioURI("/testDir/testFile"));
FileInStream fStream2 = fs.openFile(new AlluxioURI("/testDir/testDirInner/testFile2"));
byte[] read = new byte[10];
fStream1.read(read);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(10, read));
read = new byte[20];
fStream2.read(read);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(10, 20, read));
}
@Test
public void copyFromLocalTestWithFullURI() throws IOException, AlluxioException {
File testFile = generateFileContent("/srcFileURI", BufferUtils.getIncreasingByteArray(10));
String uri = "tachyon://" + mLocalAlluxioCluster.getMasterHostname() + ":"
+ mLocalAlluxioCluster.getMasterPort() + "/destFileURI";
InterpreterResult output = alluxioInterpreter.interpret("copyFromLocal " +
testFile.getPath() + " " + uri, null);
Assert.assertEquals(
"Copied " + testFile.getPath() + " to " + uri + "\n\n",
output.message());
long fileLength = fs.getStatus(new AlluxioURI("/destFileURI")).getLength();
Assert.assertEquals(10L, fileLength);
FileInStream fStream = fs.openFile(new AlluxioURI("/destFileURI"));
byte[] read = new byte[10];
fStream.read(read);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(10, read));
}
@Test
public void copyFromLocalFileToDstPathTest() throws IOException, AlluxioException {
String dataString = "copyFromLocalFileToDstPathTest";
byte[] data = dataString.getBytes();
File localDir = new File(mLocalAlluxioCluster.getAlluxioHome() + "/localDir");
localDir.mkdir();
File localFile = generateFileContent("/localDir/testFile", data);
alluxioInterpreter.interpret("mkdir /dstDir", null);
alluxioInterpreter.interpret("copyFromLocal " + localFile.getPath() + " /dstDir", null);
FileInStream fStream = fs.openFile(new AlluxioURI("/dstDir/testFile"));
long fileLength = fs.getStatus(new AlluxioURI("/dstDir/testFile")).getLength();
byte[] read = new byte[(int) fileLength];
fStream.read(read);
Assert.assertEquals(new String(read), dataString);
}
@Test
public void copyToLocalLargeTest() throws IOException {
copyToLocalWithBytes(SIZE_BYTES);
}
@Test
public void copyToLocalTest() throws IOException {
copyToLocalWithBytes(10);
}
private void copyToLocalWithBytes(int bytes) throws IOException {
FileSystemTestUtils.createByteFile(fs, "/testFile", WriteType.MUST_CACHE, 10, 10);
InterpreterResult output = alluxioInterpreter.interpret("copyToLocal /testFile " +
mLocalAlluxioCluster.getAlluxioHome() + "/testFile", null);
Assert.assertEquals(
"Copied /testFile to " + mLocalAlluxioCluster.getAlluxioHome() + "/testFile\n\n",
output.message());
fileReadTest("/testFile", 10);
}
@Test
public void countNotExistTest() throws IOException {
InterpreterResult output = alluxioInterpreter.interpret("count /NotExistFile", null);
Assert.assertEquals(Code.ERROR, output.code());
Assert.assertEquals(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/NotExistFile") + "\n",
output.message());
}
@Test
public void countTest() throws IOException {
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileA",
WriteType.CACHE_THROUGH, 10, 10);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testDir/testFileB",
WriteType.CACHE_THROUGH, 20, 20);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileB",
WriteType.CACHE_THROUGH, 30, 30);
InterpreterResult output = alluxioInterpreter.interpret("count /testRoot", null);
String expected = "";
String format = "%-25s%-25s%-15s\n";
expected += String.format(format, "File Count", "Folder Count", "Total Bytes");
expected += String.format(format, 3, 2, 60);
expected += "\n";
Assert.assertEquals(expected, output.message());
}
@Test
public void fileinfoNotExistTest() throws IOException {
InterpreterResult output = alluxioInterpreter.interpret("fileInfo /NotExistFile", null);
Assert.assertEquals(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/NotExistFile") + "\n",
output.message());
Assert.assertEquals(Code.ERROR, output.code());
}
@Test
public void locationNotExistTest() throws IOException {
InterpreterResult output = alluxioInterpreter.interpret("location /NotExistFile", null);
Assert.assertEquals(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/NotExistFile") + "\n",
output.message());
Assert.assertEquals(Code.ERROR, output.code());
}
@Test
public void lsTest() throws IOException, AlluxioException {
URIStatus[] files = new URIStatus[3];
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileA",
WriteType.MUST_CACHE, 10, 10);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testDir/testFileB",
WriteType.MUST_CACHE, 20, 20);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileC",
WriteType.THROUGH, 30, 30);
files[0] = fs.getStatus(new AlluxioURI("/testRoot/testFileA"));
files[1] = fs.getStatus(new AlluxioURI("/testRoot/testDir"));
files[2] = fs.getStatus(new AlluxioURI("/testRoot/testFileC"));
InterpreterResult output = alluxioInterpreter.interpret("ls /testRoot", null);
String expected = "";
String format = "%-10s%-25s%-15s%-5s\n";
expected += String.format(format, FormatUtils.getSizeFromBytes(10),
CommandUtils.convertMsToDate(files[0].getCreationTimeMs()), "In Memory", "/testRoot/testFileA");
expected += String.format(format, FormatUtils.getSizeFromBytes(0),
CommandUtils.convertMsToDate(files[1].getCreationTimeMs()), "", "/testRoot/testDir");
expected += String.format(format, FormatUtils.getSizeFromBytes(30),
CommandUtils.convertMsToDate(files[2].getCreationTimeMs()), "Not In Memory",
"/testRoot/testFileC");
expected += "\n";
Assert.assertEquals(Code.SUCCESS, output.code());
Assert.assertEquals(expected, output.message());
}
@Test
public void lsRecursiveTest() throws IOException, AlluxioException {
URIStatus[] files = new URIStatus[4];
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileA",
WriteType.MUST_CACHE, 10, 10);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testDir/testFileB",
WriteType.MUST_CACHE, 20, 20);
FileSystemTestUtils.createByteFile(fs, "/testRoot/testFileC",
WriteType.THROUGH, 30, 30);
files[0] = fs.getStatus(new AlluxioURI("/testRoot/testFileA"));
files[1] = fs.getStatus(new AlluxioURI("/testRoot/testDir"));
files[2] = fs.getStatus(new AlluxioURI("/testRoot/testDir/testFileB"));
files[3] = fs.getStatus(new AlluxioURI("/testRoot/testFileC"));
InterpreterResult output = alluxioInterpreter.interpret("ls -R /testRoot", null);
String expected = "";
String format = "%-10s%-25s%-15s%-5s\n";
expected +=
String.format(format, FormatUtils.getSizeFromBytes(10),
CommandUtils.convertMsToDate(files[0].getCreationTimeMs()), "In Memory",
"/testRoot/testFileA");
expected +=
String.format(format, FormatUtils.getSizeFromBytes(0),
CommandUtils.convertMsToDate(files[1].getCreationTimeMs()), "", "/testRoot/testDir");
expected +=
String.format(format, FormatUtils.getSizeFromBytes(20),
CommandUtils.convertMsToDate(files[2].getCreationTimeMs()), "In Memory",
"/testRoot/testDir/testFileB");
expected +=
String.format(format, FormatUtils.getSizeFromBytes(30),
CommandUtils.convertMsToDate(files[3].getCreationTimeMs()), "Not In Memory",
"/testRoot/testFileC");
expected += "\n";
Assert.assertEquals(expected, output.message());
}
@Test
public void mkdirComplexPathTest() throws IOException, AlluxioException {
InterpreterResult output = alluxioInterpreter.interpret(
"mkdir /Complex!@#$%^&*()-_=+[]{};\"'<>,.?/File", null);
boolean existsDir = fs.exists(new AlluxioURI("/Complex!@#$%^&*()-_=+[]{};\"'<>,.?/File"));
Assert.assertEquals(
"Successfully created directory /Complex!@#$%^&*()-_=+[]{};\"'<>,.?/File\n\n",
output.message());
Assert.assertTrue(existsDir);
}
@Test
public void mkdirExistingTest() throws IOException {
String command = "mkdir /festFile1";
Assert.assertEquals(Code.SUCCESS, alluxioInterpreter.interpret(command, null).code());
Assert.assertEquals(Code.ERROR, alluxioInterpreter.interpret(command, null).code());
}
@Test
public void mkdirInvalidPathTest() throws IOException {
Assert.assertEquals(
Code.ERROR,
alluxioInterpreter.interpret("mkdir /test File Invalid Path", null).code());
}
@Test
public void mkdirShortPathTest() throws IOException, AlluxioException {
InterpreterResult output = alluxioInterpreter.interpret("mkdir /root/testFile1", null);
boolean existsDir = fs.exists(new AlluxioURI("/root/testFile1"));
Assert.assertEquals(
"Successfully created directory /root/testFile1\n\n",
output.message());
Assert.assertTrue(existsDir);
}
@Test
public void mkdirTest() throws IOException, AlluxioException {
String qualifiedPath =
"tachyon://" + mLocalAlluxioCluster.getMasterHostname() + ":"
+ mLocalAlluxioCluster.getMasterPort() + "/root/testFile1";
InterpreterResult output = alluxioInterpreter.interpret("mkdir " + qualifiedPath, null);
boolean existsDir = fs.exists(new AlluxioURI("/root/testFile1"));
Assert.assertEquals(
"Successfully created directory " + qualifiedPath + "\n\n",
output.message());
Assert.assertTrue(existsDir);
}
private File generateFileContent(String path, byte[] toWrite)
throws IOException {
File testFile = new File(mLocalAlluxioCluster.getAlluxioHome() + path);
testFile.createNewFile();
FileOutputStream fos = new FileOutputStream(testFile);
fos.write(toWrite);
fos.close();
return testFile;
}
private void fileReadTest(String fileName, int size) throws IOException {
File testFile = new File(PathUtils.concatPath(mLocalAlluxioCluster.getAlluxioHome(), fileName));
FileInputStream fis = new FileInputStream(testFile);
byte[] read = new byte[size];
fis.read(read);
fis.close();
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(size, read));
}
}

View file

@ -19,8 +19,9 @@
# List of users with their password allowed to access Zeppelin.
# To use a different strategy (LDAP / Database / ...) check the shiro doc at http://shiro.apache.org/configuration.html#Configuration-INISections
admin = password1
user1 = password2
user2 = password3
user1 = password2, role1, role2
user2 = password3, role3
user3 = password4, role2
# Sample LDAP configuration, for user Authentication, currently tested for single Realm
[main]

View file

@ -138,7 +138,7 @@
<property>
<name>zeppelin.interpreters</name>
<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.phoenix.PhoenixInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.tachyon.TachyonInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter</value>
<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.phoenix.PhoenixInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter</value>
<description>Comma separated interpreter configurations. First interpreter become a default</description>
</property>

View file

@ -26,6 +26,8 @@
<li><a href="{{BASE_PATH}}/install/yarn_install.html">YARN Install</a></li>
<li><a href="{{BASE_PATH}}/install/virtual_machine.html">Virtual Machine Install</a></li>
<li role="separator" class="divider"></li>
<li><a href="{{BASE_PATH}}/install/upgrade.html">Upgrade Version</a></li>
<li role="separator" class="divider"></li>
<!-- li><span><b>Tutorial</b><span></li -->
<li><a href="{{BASE_PATH}}/tutorial/tutorial.html">Tutorial</a></li>
<li role="separator" class="divider"></li>
@ -53,7 +55,7 @@
<li><a href="{{BASE_PATH}}/interpreter/scalding.html">Scalding</a></li>
<li><a href="{{BASE_PATH}}/pleasecontribute.html">Shell</a></li>
<li><a href="{{BASE_PATH}}/interpreter/spark.html">Spark</a></li>
<li><a href="{{BASE_PATH}}/interpreter/tachyon.html">Tachyon</a></li>
<li><a href="{{BASE_PATH}}/interpreter/alluxio.html">Alluxio</a></li>
<li><a href="{{BASE_PATH}}/pleasecontribute.html">Tajo</a></li>
<li role="separator" class="divider"></li>
<li><a href="{{BASE_PATH}}/manual/dynamicinterpreterload.html">Dynamic Interpreter Loading</a></li>
@ -84,6 +86,12 @@
<li><a href="{{BASE_PATH}}/rest-api/rest-notebook.html">Notebook API</a></li>
<li><a href="{{BASE_PATH}}/rest-api/rest-configuration.html">Configuration API</a></li>
<li role="separator" class="divider"></li>
<!-- li><span><b>Security</b><span></li -->
<li><a href="{{BASE_PATH}}/security/overview.html">Security Overview</a></li>
<li><a href="{{BASE_PATH}}/security/authentication.html">Authentication</a></li>
<li><a href="{{BASE_PATH}}/security/notebook_authorization.html">Notebook Authorization</a></li>
<li><a href="{{BASE_PATH}}/security/interpreter_authorization.html">Interpreter Authorization</a></li>
<li role="separator" class="divider"></li>
<!-- li><span><b>Development</b><span></li -->
<li><a href="{{BASE_PATH}}/development/writingzeppelininterpreter.html">Writing Zeppelin Interpreter</a></li>
<li><a href="{{BASE_PATH}}/development/howtocontribute.html">How to contribute (code)</a></li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -22,12 +22,16 @@ limitations under the License.
### What is Zeppelin Interpreter
Zeppelin Interpreter is a language backend. For example to use scala code in Zeppelin, you need scala interpreter.
Every Interpreter belongs to an InterpreterGroup. InterpreterGroup is a unit of start/stop interpreter.
Every Interpreter belongs to an InterpreterGroup.
Interpreters in the same InterpreterGroup can reference each other. For example, SparkSqlInterpreter can reference SparkInterpreter to get SparkContext from it while they're in the same group.
<img class="img-responsive" style="width:50%; border: 1px solid #ecf0f1;" height="auto" src="/assets/themes/zeppelin/img/interpreter.png" />
All Interpreters in the same interpreter group are launched in a single, separate JVM process. The Interpreter communicates with Zeppelin engine via thrift.
InterpreterSetting is configuration of a given InterpreterGroup and a unit of start/stop interpreter.
All Interpreters in the same InterpreterSetting are launched in a single, separate JVM process. The Interpreter communicates with Zeppelin engine via thrift.
In 'Separate Interpreter for each note' mode, new Interpreter instance will be created per notebook. But it still runs on the same JVM while they're in the same InterpreterSettings.
### Make your own Interpreter

44
docs/install/upgrade.md Normal file
View file

@ -0,0 +1,44 @@
---
layout: page
title: "Manual upgrade procedure for Zeppelin"
description: ""
group: install
---
<!--
Licensed 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.
-->
{% include JB/setup %}
## Manual upgrade procedure for Zeppelin
Basically, newer version of Zeppelin works with previous version notebook directory and configurations.
So, copying `notebook` and `conf` directory should be enough.
### Instructions
1. Stop Zeppelin
```
bin/zeppelin-daemon.sh stop
```
1. Copy your `notebook` and `conf` directory into a backup directory
1. Download newer version of Zeppelin and Install. See [Install page](./install.html)
1. Copy backup `notebook` and `conf` directory into newer version of Zeppelin `notebook` and `conf` directory
1. Start Zeppelin
```
bin/zeppelin-daemon.sh start
```

View file

@ -1,13 +1,13 @@
---
layout: page
title: "Tachyon Interpreter"
description: "Tachyon Interpreter"
title: "Alluxio Interpreter"
description: "Alluxio Interpreter"
group: manual
---
{% include JB/setup %}
## Tachyon Interpreter for Apache Zeppelin
[Tachyon](http://tachyon-project.org/) is a memory-centric distributed storage system enabling reliable data sharing at memory-speed across cluster frameworks.
## Alluxio Interpreter for Apache Zeppelin
[Alluxio](http://alluxio.org/) is a memory-centric distributed storage system enabling reliable data sharing at memory-speed across cluster frameworks.
## Configuration
<table class="table-configuration">
@ -17,32 +17,32 @@ group: manual
<th>Description</th>
</tr>
<tr>
<td>tachyon.master.hostname</td>
<td>alluxio.master.hostname</td>
<td>localhost</td>
<td>Tachyon master hostname</td>
<td>Alluxio master hostname</td>
</tr>
<tr>
<td>tachyon.master.port</td>
<td>alluxio.master.port</td>
<td>19998</td>
<td>Tachyon master port</td>
<td>Alluxio master port</td>
</tr>
</table>
## Enabling Tachyon Interpreter
In a notebook, to enable the **Tachyon** interpreter, click on the **Gear** icon and select **Tachyon**.
## Enabling Alluxio Interpreter
In a notebook, to enable the **Alluxio** interpreter, click on the **Gear** icon and select **Alluxio**.
## Using the Tachyon Interpreter
In a paragraph, use `%tachyon` to select the **Tachyon** interpreter and then input all commands.
## Using the Alluxio Interpreter
In a paragraph, use `%alluxio` to select the **Alluxio** interpreter and then input all commands.
```bash
%tachyon
%alluxio
help
```
> **Tip :** Use ( Ctrl + . ) for autocompletion.
## Interpreter Commands
The **Tachyon** interpreter accepts the following commands.
The **Alluxio** interpreter accepts the following commands.
<center>
<table class="table-configuration">
@ -51,17 +51,36 @@ The **Tachyon** interpreter accepts the following commands.
<th>Syntax</th>
<th>Description</th>
</tr>
<tr>
<td>cat</td>
<td>cat "path"</td>
<td>Print the content of the file to the console.</td>
</tr>
<tr>
<td>chgrp</td>
<td>chgrp "group" "path"</td>
<td>Change the group of the directory or file.</td>
</tr>
<tr>
<td>chmod</td>
<td>chmod "permission" "path"</td>
<td>Change the permission of the directory or file.</td>
</tr>
<tr>
<td>chown</td>
<td>chown "owner" "path"</td>
<td>Change the owner of the directory or file.</td>
</tr>
<tr>
<td>copyFromLocal</td>
<td>copyFromLocal "source path" "remote path"</td>
<td>Copy the specified file specified by "source path" to the path specified by "remote path".
<td>Copy the specified file specified by "source path" to the path specified by "remote path".
This command will fail if "remote path" already exists.</td>
</tr>
<tr>
<td>copyToLocal</td>
<td>copyToLocal "remote path" "local path"</td>
<td>Copy the specified file from the path specified by "remote source" to a local
destination.</td>
<td>Copy the specified file from the path specified by "remote path" to a local destination.</td>
</tr>
<tr>
<td>count</td>
@ -74,35 +93,35 @@ The **Tachyon** interpreter accepts the following commands.
<td>Display the size of a file or a directory specified by the input path.</td>
</tr>
<tr>
<td>fileinfo</td>
<td>fileinfo "path"</td>
<td>fileInfo</td>
<td>fileInfo "path"</td>
<td>Print the information of the blocks of a specified file.</td>
</tr>
<tr>
<td>free</td>
<td>free "path"</td>
<td>Free a file or all files under a directory from Tachyon. If the file/directory is also
<td>Free a file or all files under a directory from Alluxio. If the file/directory is also
in under storage, it will still be available there.</td>
</tr>
<tr>
<td>getCapacityBytes</td>
<td>getCapacityBytes</td>
<td>Get the capacity of the TachyonFS.</td>
<td>Get the capacity of the AlluxioFS.</td>
</tr>
<tr>
<td>getUsedBytes</td>
<td>getUsedBytes</td>
<td>Get number of bytes used in the TachyonFS.</td>
<td>Get number of bytes used in the AlluxioFS.</td>
</tr>
<tr>
<td>load</td>
<td>load "path"</td>
<td>Load the data of a file or a directory from under storage into Tachyon.</td>
<td>Load the data of a file or a directory from under storage into Alluxio.</td>
</tr>
<tr>
<td>loadMetadata</td>
<td>loadMetadata "path"</td>
<td>Load the metadata of a file or a directory from under storage into Tachyon.</td>
<td>Load the metadata of a file or a directory from under storage into Alluxio.</td>
</tr>
<tr>
<td>location</td>
@ -115,24 +134,19 @@ The **Tachyon** interpreter accepts the following commands.
<td>List all the files and directories directly under the given path with information such as
size.</td>
</tr>
<tr>
<td>lsr</td>
<td>lsr "path"</td>
<td>Recursively list all the files and directories under the given path with information such
as size.</td>
</tr>
<tr>
<td>mkdir</td>
<td>mkdir "path"</td>
<td>Create a directory under the given path, along with any necessary parent directories. This
command will fail if the given path already exists.</td>
<td>mkdir "path1" ... "pathn"</td>
<td>Create directory(ies) under the given paths, along with any necessary parent directories.
Multiple paths separated by spaces or tabs. This command will fail if any of the given paths
already exist.</td>
</tr>
<tr>
<td>mount</td>
<td>mount "path" "uri"</td>
<td>Mount the underlying file system path "uri" into the Tachyon namespace as "path". The "path"
<td>Mount the underlying file system path "uri" into the Alluxio namespace as "path". The "path"
is assumed not to exist and is created by the operation. No data or metadata is loaded from under
storage into Tachyon. After a path is mounted, operations on objects under the mounted path are
storage into Alluxio. After a path is mounted, operations on objects under the mounted path are
mirror to the mounted under storage.</td>
</tr>
<tr>
@ -141,6 +155,11 @@ The **Tachyon** interpreter accepts the following commands.
<td>Move a file or directory specified by "source" to a new location "destination". This command
will fail if "destination" already exists.</td>
</tr>
<tr>
<td>persist</td>
<td>persist "path"</td>
<td>Persist a file or directory currently stored only in Alluxio to the underlying file system.</td>
</tr>
<tr>
<td>pin</td>
<td>pin "path"</td>
@ -152,22 +171,15 @@ The **Tachyon** interpreter accepts the following commands.
<td>report "path"</td>
<td>Report to the master that a file is lost.</td>
</tr>
<tr>
<td>request</td>
<td>request "path" "dependency ID"</td>
<td>Request the file for a given dependency ID.</td>
</tr>
<tr>
<td>rm</td>
<td>rm "path"</td>
<td>Remove a file. This command will fail if the given path is a directory rather than a
file.</td>
<td>Remove a file. This command will fail if the given path is a directory rather than a file.</td>
</tr>
<tr>
<td>rmr</td>
<td>rmr "path"</td>
<td>Remove a file, or a directory with all the files and sub-directories that this directory
contains.</td>
<td>setTtl</td>
<td>setTtl "time"</td>
<td>Set the TTL (time to live) in milliseconds to a file.</td>
</tr>
<tr>
<td>tail</td>
@ -182,35 +194,40 @@ The **Tachyon** interpreter accepts the following commands.
<tr>
<td>unmount</td>
<td>unmount "path"</td>
<td>Unmount the underlying file system path mounted in the Tachyon namespace as "path". Tachyon
objects under "path" are removed from Tachyon, but they still exist in the previously mounted
<td>Unmount the underlying file system path mounted in the Alluxio namespace as "path". Alluxio
objects under "path" are removed from Alluxio, but they still exist in the previously mounted
under storage.</td>
</tr>
<tr>
<td>unpin</td>
<td>unpin "path"</td>
<td>Unpin the given file to allow Tachyon to evict this file again. If the given path is a
<td>Unpin the given file to allow Alluxio to evict this file again. If the given path is a
directory, it recursively unpins all files contained and any new files created within this
directory.</td>
</tr>
<tr>
<td>unsetTtl</td>
<td>unsetTtl</td>
<td>Remove the TTL (time to live) setting from a file.</td>
</tr>
</table>
</center>
## How to test it's working
Be sure to have configured correctly the Tachyon interpreter, then open a new paragraph and type one of the above commands.
Be sure to have configured correctly the Alluxio interpreter, then open a new paragraph and type one of the above commands.
Below a simple example to show how to interact with Tachyon interpreter.
Below a simple example to show how to interact with Alluxio interpreter.
Following steps are performed:
* using sh interpreter a new text file is created on local machine
* using Tachyon interpreter:
* is listed the content of the tfs (Tachyon File System) root
* the file previously created is copied to tfs
* is listed again the content of the tfs root to check the existence of the new copied file
* using Alluxio interpreter:
* is listed the content of the afs (Alluxio File System) root
* the file previously created is copied to afs
* is listed again the content of the afs root to check the existence of the new copied file
* is showed the content of the copied file (using the tail command)
* the file previously copied to tfs is copied to local machine
* using sh interpreter it's checked the existence of the new file copied from Tachyon and its content is showed
* the file previously copied to afs is copied to local machine
* using sh interpreter it's checked the existence of the new file copied from Alluxio and its content is showed
<center>
![Tachyon Interpreter Example](../assets/themes/zeppelin/img/docs-img/tachyon-example.png)
![Alluxio Interpreter Example](../assets/themes/zeppelin/img/docs-img/alluxio-example.png)
</center>

View file

@ -263,6 +263,11 @@ select * from ${table=defaultTableName} where text like '%${search}%'
To learn more about dynamic form, checkout [Dynamic Form](../manual/dynamicform.html).
### Separate Interpreter for each note
In 'Separate Interpreter for each note' mode, SparkInterpreter creates scala compiler per each notebook. However it still shares the single SparkContext.
## Setting up Zeppelin with Kerberos
Logical setup with Zeppelin, Kerberos Distribution Center (KDC), and Spark on YARN:

View file

@ -54,6 +54,16 @@ Also you can separate option's display name and value, using _${formName=default
<img src="/assets/themes/zeppelin/img/screenshots/form_select_displayname.png" />
#### Checkbox form
For multi-selection, you can create a checkbox form using _${checkbox:formName=defaultValue1|defaultValue2...,option1|option2...}_. The variable will be substituted by a comma-separated string based on the selected items. For example:
<img src="/assets/themes/zeppelin/img/screenshots/form_checkbox.png">
Besides, you can specify the delimiter using _${checkbox(delimiter):formName=...}_:
<img src="/assets/themes/zeppelin/img/screenshots/form_checkbox_delimiter.png">
### Creates Programmatically
Some language backend uses programmatic way to create form. For example [ZeppelinContext](../interpreter/spark.html#zeppelincontext) provides form creation API
@ -134,3 +144,26 @@ print("Hello "+z.select("day", [("1","mon"),
</div>
</div>
<img src="/assets/themes/zeppelin/img/screenshots/form_select_prog.png" />
#### Checkbox form
<div class="codetabs">
<div data-lang="scala" markdown="1">
{% highlight scala %}
%spark
val options = Seq(("apple","Apple"), ("banana","Banana"), ("orange","Orange"))
println("Hello "+z.checkbox("fruit", options).mkString(" and "))
{% endhighlight %}
</div>
<div data-lang="python" markdown="1">
{% highlight python %}
%pyspark
options = [("apple","Apple"), ("banana","Banana"), ("orange","Orange")]
print("Hello "+ " and ".join(z.checkbox("fruit", options, ["apple"])))
{% endhighlight %}
</div>
</div>
<img src="/assets/themes/zeppelin/img/screenshots/form_checkbox_prog.png" />

View file

@ -32,10 +32,16 @@ When you click the ```+Create``` button in the interpreter page, the interpreter
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_create.png">
## What is Zeppelin Interpreter Setting?
Zeppelin interpreter setting is the configuration of a given interpreter on Zeppelin server. For example, the properties are required for hive JDBC interpreter to connect to the Hive server.
Zeppelin interpreter setting is the configuration of a given interpreter on Zeppelin server. For example, the properties are required for hive JDBC interpreter to connect to the Hive server.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_setting.png">
Each notebook can be binded to multiple Interpreter Settings using setting icon on upper right corner of the notebook.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_binding.png" width="800px">
## What is Zeppelin Interpreter Group?
Every Interpreter is belonged to an **Interpreter Group**. Interpreter Group is a unit of start/stop interpreter.
By default, every interpreter is belonged to a single group, but the group might contain more interpreters. For example, Spark interpreter group is including Spark support, pySpark, SparkSQL and the dependency loader.
@ -44,3 +50,12 @@ Technically, Zeppelin interpreters from the same group are running in the same J
Each interpreters is belonged to a single group and registered together. All of their properties are listed in the interpreter setting like below image.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_setting_spark.png">
## Interpreter binding mode
Each Interpreter Setting can choose one of two different interpreter binding mode.
Shared mode (default) and 'Separate Interpreter for each note' mode. In shared mode, every notebook binded to the Interpreter Setting will share the single Interpreter instance. In 'Separate Interpreter for each note' mode, each notebook will create new Interpreter instance. Therefore each notebook will have fresh new Interpreter environment.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_persession.png" width="400px">

View file

@ -0,0 +1,31 @@
---
layout: page
title: "Authentication"
description: "Authentication"
group: security
---
<!--
Licensed 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.
-->
# Authentication
Authentication is company-specific.
One option is to use [Basic Access Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
Another option is to have an authentication server that can verify user credentials in an LDAP server.
If an incoming request to the Zeppelin server does not have a cookie with user information encrypted with the authentication server public key, the user
is redirected to the authentication server. Once the user is verified, the authentication server redirects the browser to a specific
URL in the Zeppelin server which sets the authentication cookie in the browser.
The end result is that all requests to the Zeppelin
web server have the authentication cookie which contains user and groups information.

View file

@ -0,0 +1,34 @@
---
layout: page
title: "Notebook Authorization"
description: "Notebook Authorization"
group: security
---
<!--
Licensed 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.
-->
# Interpreter and Data Source Authorization
## Interpreter Authorization
Interpreter authorization involves permissions like creating an interpreter and execution queries using it.
## Data Source Authorization
Data source authorization involves authenticating to the data source like a Mysql database and letting it determine user permissions.
For the Hive interpreter, we need to maintain per-user connection pools.
The interpret method takes the user string as parameter and executes the jdbc call using a connection in the user's connection pool.
In case of Presto, we don't need password if the Presto DB server runs backend code using HDFS authorization for the user.
For databases like Vertica and Mysql we have to store password information for users.

View file

@ -0,0 +1,37 @@
---
layout: page
title: "Notebook Authorization"
description: "Notebook Authorization"
group: security
---
<!--
Licensed 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.
-->
# Notebook Authorization
We assume that there is an authentication component that associates a user string and a set of group strings with every NotebookSocket.
Each note has the following:
* set of owner entities (users or groups)
* set of reader entities (users or groups)
* set of writer entities (users or groups)
If a set is empty, it means that any user can perform that operation.
The NotebookServer classifies every Note operation into three categories: read, write, manage.
Before executing a Note operation, it checks if the user and the groups associated with the NotebookSocket have permissions. For example, before executing an read
operation, it checks if the user and the groups have at least one entity that belongs to the reader entities.
To initialize and modify note permissions, we provide UI like "Interpreter binding". The user inputs comma separated entities for owners, readers and writers.
We execute a rest api call with this information. In the backend we get the user information for the connection and allow the operation if the user and groups
associated with the current user have at least one entity that belongs to owner entities for the note.

28
docs/security/overview.md Normal file
View file

@ -0,0 +1,28 @@
---
layout: page
title: "Security Overview"
description: "Security Overview"
group: security
---
<!--
Licensed 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.
-->
{% include JB/setup %}
# Security Overview
There are three aspects to Zeppelin security:
* Authentication: is the user who they say they are? [More](authentication.html)
* Notebook authorization: does the user have permissions to read or write to a note? [More](notebook_authorization.html)
* Interpreter and data source authorization: does the user have permissions to perform interpreter operations or access data source objects? [More](interpreter_authorization.html)

View file

@ -105,7 +105,7 @@
<module>lens</module>
<module>cassandra</module>
<module>elasticsearch</module>
<module>tachyon</module>
<module>alluxio</module>
<module>zeppelin-web</module>
<module>zeppelin-server</module>
<module>zeppelin-distribution</module>
@ -447,6 +447,7 @@
<exclude>**/.idea/</exclude>
<exclude>**/*.iml</exclude>
<exclude>.git/</exclude>
<exclude>.github/*</exclude>
<exclude>.gitignore</exclude>
<exclude>.repository/</exclude>
<exclude>**/*.diff</exclude>

View file

@ -299,23 +299,25 @@ public class DepInterpreter extends Interpreter {
if (intpGroup == null) {
return null;
}
synchronized (intpGroup) {
for (Interpreter intp : intpGroup){
if (intp.getClassName().equals(SparkInterpreter.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
return (SparkInterpreter) p;
}
}
Interpreter p = getInterpreterInTheSameSessionByClassName(SparkInterpreter.class.getName());
if (p == null) {
return null;
}
return null;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
return (SparkInterpreter) p;
}
@Override
public Scheduler getScheduler() {
return getSparkInterpreter().getScheduler();
SparkInterpreter sparkInterpreter = getSparkInterpreter();
if (sparkInterpreter != null) {
return getSparkInterpreter().getScheduler();
} else {
return null;
}
}
}

View file

@ -494,23 +494,18 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
private SparkInterpreter getSparkInterpreter() {
InterpreterGroup intpGroup = getInterpreterGroup();
LazyOpenInterpreter lazy = null;
SparkInterpreter spark = null;
synchronized (intpGroup) {
for (Interpreter intp : getInterpreterGroup()){
if (intp.getClassName().equals(SparkInterpreter.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
if (p instanceof LazyOpenInterpreter) {
lazy = (LazyOpenInterpreter) p;
}
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
spark = (SparkInterpreter) p;
}
Interpreter p = getInterpreterInTheSameSessionByClassName(SparkInterpreter.class.getName());
while (p instanceof WrappedInterpreter) {
if (p instanceof LazyOpenInterpreter) {
lazy = (LazyOpenInterpreter) p;
}
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
spark = (SparkInterpreter) p;
if (lazy != null) {
lazy.open();
}
@ -554,20 +549,15 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
}
private DepInterpreter getDepInterpreter() {
InterpreterGroup intpGroup = getInterpreterGroup();
if (intpGroup == null) return null;
synchronized (intpGroup) {
for (Interpreter intp : intpGroup) {
if (intp.getClassName().equals(DepInterpreter.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
return (DepInterpreter) p;
}
}
Interpreter p = getInterpreterInTheSameSessionByClassName(DepInterpreter.class.getName());
if (p == null) {
return null;
}
return null;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
return (DepInterpreter) p;
}

View file

@ -20,11 +20,13 @@ package org.apache.zeppelin.spark;
import java.io.File;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Joiner;
@ -44,7 +46,6 @@ import org.apache.spark.ui.jobs.JobProgressListener;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
@ -57,17 +58,15 @@ import org.apache.zeppelin.spark.dep.SparkDependencyResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.Console;
import scala.*;
import scala.Enumeration.Value;
import scala.None;
import scala.Some;
import scala.Tuple2;
import scala.collection.Iterator;
import scala.collection.JavaConversions;
import scala.collection.JavaConverters;
import scala.collection.Seq;
import scala.collection.mutable.HashMap;
import scala.collection.mutable.HashSet;
import scala.reflect.io.AbstractFile;
import scala.tools.nsc.Settings;
import scala.tools.nsc.interpreter.Completion.Candidates;
import scala.tools.nsc.interpreter.Completion.ScalaCompleter;
@ -113,16 +112,19 @@ public class SparkInterpreter extends Interpreter {
private ZeppelinContext z;
private SparkILoop interpreter;
private SparkIMain intp;
private SparkContext sc;
private static SparkContext sc;
private static SQLContext sqlc;
private static SparkEnv env;
private static JobProgressListener sparkListener;
private static AbstractFile classOutputDir;
private static Integer sharedInterpreterLock = new Integer(0);
private static AtomicInteger numReferenceOfSparkContext = new AtomicInteger(0);
private SparkOutputStream out;
private SQLContext sqlc;
private SparkDependencyResolver dep;
private SparkJLineCompletion completor;
private JobProgressListener sparkListener;
private Map<String, Object> binder;
private SparkEnv env;
private SparkVersion sparkVersion;
@ -139,17 +141,21 @@ public class SparkInterpreter extends Interpreter {
sparkListener = setupListeners(this.sc);
}
public synchronized SparkContext getSparkContext() {
if (sc == null) {
sc = createSparkContext();
env = SparkEnv.get();
sparkListener = setupListeners(sc);
public SparkContext getSparkContext() {
synchronized (sharedInterpreterLock) {
if (sc == null) {
sc = createSparkContext();
env = SparkEnv.get();
sparkListener = setupListeners(sc);
}
return sc;
}
return sc;
}
public boolean isSparkContextInitialized() {
return sc != null;
synchronized (sharedInterpreterLock) {
return sc != null;
}
}
static JobProgressListener setupListeners(SparkContext context) {
@ -192,33 +198,34 @@ public class SparkInterpreter extends Interpreter {
}
private boolean useHiveContext() {
return Boolean.parseBoolean(getProperty("zeppelin.spark.useHiveContext"));
return java.lang.Boolean.parseBoolean(getProperty("zeppelin.spark.useHiveContext"));
}
public SQLContext getSQLContext() {
if (sqlc == null) {
if (useHiveContext()) {
String name = "org.apache.spark.sql.hive.HiveContext";
Constructor<?> hc;
try {
hc = getClass().getClassLoader().loadClass(name)
.getConstructor(SparkContext.class);
sqlc = (SQLContext) hc.newInstance(getSparkContext());
} catch (NoSuchMethodException | SecurityException
| ClassNotFoundException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
logger.warn("Can't create HiveContext. Fallback to SQLContext", e);
// when hive dependency is not loaded, it'll fail.
// in this case SQLContext can be used.
synchronized (sharedInterpreterLock) {
if (sqlc == null) {
if (useHiveContext()) {
String name = "org.apache.spark.sql.hive.HiveContext";
Constructor<?> hc;
try {
hc = getClass().getClassLoader().loadClass(name)
.getConstructor(SparkContext.class);
sqlc = (SQLContext) hc.newInstance(getSparkContext());
} catch (NoSuchMethodException | SecurityException
| ClassNotFoundException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
logger.warn("Can't create HiveContext. Fallback to SQLContext", e);
// when hive dependency is not loaded, it'll fail.
// in this case SQLContext can be used.
sqlc = new SQLContext(getSparkContext());
}
} else {
sqlc = new SQLContext(getSparkContext());
}
} else {
sqlc = new SQLContext(getSparkContext());
}
return sqlc;
}
return sqlc;
}
public SparkDependencyResolver getDependencyResolver() {
@ -232,20 +239,15 @@ public class SparkInterpreter extends Interpreter {
}
private DepInterpreter getDepInterpreter() {
InterpreterGroup intpGroup = getInterpreterGroup();
if (intpGroup == null) return null;
synchronized (intpGroup) {
for (Interpreter intp : intpGroup) {
if (intp.getClassName().equals(DepInterpreter.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
return (DepInterpreter) p;
}
}
Interpreter p = getInterpreterInTheSameSessionByClassName(DepInterpreter.class.getName());
if (p == null) {
return null;
}
return null;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
return (DepInterpreter) p;
}
public SparkContext createSparkContext() {
@ -477,7 +479,9 @@ public class SparkInterpreter extends Interpreter {
b.v_$eq(true);
settings.scala$tools$nsc$settings$StandardScalaSettings$_setter_$usejavacp_$eq(b);
/* spark interpreter */
System.setProperty("scala.repl.name.line", "line" + this.hashCode() + "$");
/* create scala repl */
this.interpreter = new SparkILoop(null, new PrintWriter(out));
interpreter.settings_$eq(settings);
@ -488,21 +492,39 @@ public class SparkInterpreter extends Interpreter {
intp.setContextClassLoader();
intp.initializeSynchronous();
completor = new SparkJLineCompletion(intp);
synchronized (sharedInterpreterLock) {
if (classOutputDir == null) {
classOutputDir = settings.outputDirs().getSingleOutput().get();
} else {
// change SparkIMain class output dir
settings.outputDirs().setSingleOutput(classOutputDir);
ClassLoader cl = intp.classLoader();
sc = getSparkContext();
if (sc.getPoolForName("fair").isEmpty()) {
Value schedulingMode = org.apache.spark.scheduler.SchedulingMode.FAIR();
int minimumShare = 0;
int weight = 1;
Pool pool = new Pool("fair", schedulingMode, minimumShare, weight);
sc.taskScheduler().rootPool().addSchedulable(pool);
try {
Field rootField = cl.getClass().getSuperclass().getDeclaredField("root");
rootField.setAccessible(true);
rootField.set(cl, classOutputDir);
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.error(e.getMessage(), e);
}
}
completor = new SparkJLineCompletion(intp);
sc = getSparkContext();
if (sc.getPoolForName("fair").isEmpty()) {
Value schedulingMode = org.apache.spark.scheduler.SchedulingMode.FAIR();
int minimumShare = 0;
int weight = 1;
Pool pool = new Pool("fair", schedulingMode, minimumShare, weight);
sc.taskScheduler().rootPool().addSchedulable(pool);
}
sparkVersion = SparkVersion.fromVersionString(sc.version());
sqlc = getSQLContext();
}
sparkVersion = SparkVersion.fromVersionString(sc.version());
sqlc = getSQLContext();
dep = getDependencyResolver();
z = new ZeppelinContext(sc, sqlc, null, dep,
@ -594,6 +616,8 @@ public class SparkInterpreter extends Interpreter {
}
}
}
numReferenceOfSparkContext.incrementAndGet();
}
private List<File> currentClassPath() {
@ -916,8 +940,12 @@ public class SparkInterpreter extends Interpreter {
@Override
public void close() {
sc.stop();
sc = null;
logger.info("Close interpreter");
if (numReferenceOfSparkContext.decrementAndGet() == 0) {
sc.stop();
sc = null;
}
intp.close();
}

View file

@ -79,23 +79,18 @@ public class SparkSqlInterpreter extends Interpreter {
}
private SparkInterpreter getSparkInterpreter() {
InterpreterGroup intpGroup = getInterpreterGroup();
LazyOpenInterpreter lazy = null;
SparkInterpreter spark = null;
synchronized (intpGroup) {
for (Interpreter intp : getInterpreterGroup()){
if (intp.getClassName().equals(SparkInterpreter.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
if (p instanceof LazyOpenInterpreter) {
lazy = (LazyOpenInterpreter) p;
}
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
spark = (SparkInterpreter) p;
}
Interpreter p = getInterpreterInTheSameSessionByClassName(SparkInterpreter.class.getName());
while (p instanceof WrappedInterpreter) {
if (p instanceof LazyOpenInterpreter) {
lazy = (LazyOpenInterpreter) p;
}
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
spark = (SparkInterpreter) p;
if (lazy != null) {
lazy.open();
}
@ -179,15 +174,14 @@ public class SparkSqlInterpreter extends Interpreter {
// It's because of scheduler is not created yet, and scheduler is created by this function.
// Therefore, we can still use getSparkInterpreter() here, but it's better and safe
// to getSparkInterpreter without opening it.
for (Interpreter intp : getInterpreterGroup()) {
if (intp.getClassName().equals(SparkInterpreter.class.getName())) {
Interpreter p = intp;
return p.getScheduler();
} else {
continue;
}
Interpreter intp =
getInterpreterInTheSameSessionByClassName(SparkInterpreter.class.getName());
if (intp != null) {
return intp.getScheduler();
} else {
return null;
}
throw new InterpreterException("Can't find SparkInterpreter");
}
}

View file

@ -17,7 +17,9 @@
package org.apache.zeppelin.spark;
import static scala.collection.JavaConversions.asJavaCollection;
import static scala.collection.JavaConversions.asJavaIterable;
import static scala.collection.JavaConversions.asScalaIterable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@ -84,6 +86,27 @@ public class ZeppelinContext {
public Object select(String name, Object defaultValue,
scala.collection.Iterable<Tuple2<Object, String>> options) {
return gui.select(name, defaultValue, tuplesToParamOptions(options));
}
public scala.collection.Iterable<Object> checkbox(String name,
scala.collection.Iterable<Tuple2<Object, String>> options) {
List<Object> allChecked = new LinkedList<Object>();
for (Tuple2<Object, String> option : asJavaIterable(options)) {
allChecked.add(option._1());
}
return checkbox(name, asScalaIterable(allChecked), options);
}
public scala.collection.Iterable<Object> checkbox(String name,
scala.collection.Iterable<Object> defaultChecked,
scala.collection.Iterable<Tuple2<Object, String>> options) {
return asScalaIterable(gui.checkbox(name, asJavaCollection(defaultChecked),
tuplesToParamOptions(options)));
}
private ParamOption[] tuplesToParamOptions(
scala.collection.Iterable<Tuple2<Object, String>> options) {
int n = options.size();
ParamOption[] paramOptions = new ParamOption[n];
Iterator<Tuple2<Object, String>> it = asJavaIterable(options).iterator();
@ -94,7 +117,7 @@ public class ZeppelinContext {
paramOptions[i++] = new ParamOption(valueAndDisplayValue._1(), valueAndDisplayValue._2());
}
return gui.select(name, defaultValue, paramOptions);
return paramOptions;
}
public void setGui(GUI o) {

View file

@ -84,6 +84,16 @@ class PyZeppelinContext(dict):
iterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(tuples)
return self.z.select(name, defaultValue, iterables)
def checkbox(self, name, options, defaultChecked = None):
if defaultChecked is None:
defaultChecked = map(lambda items: items[0], options)
optionTuples = map(lambda items: self.__tupleToScalaTuple2(items), options)
optionIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(optionTuples)
defaultCheckedIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(defaultChecked)
checkedIterables = self.z.checkbox(name, defaultCheckedIterables, optionIterables)
return gateway.jvm.scala.collection.JavaConversions.asJavaCollection(checkedIterables)
def __tupleToScalaTuple2(self, tuple):
if (len(tuple) == 2):
return gateway.jvm.scala.Tuple2(tuple[0], tuple[1])

View file

@ -52,8 +52,9 @@ public class DepInterpreterTest {
dep.open();
InterpreterGroup intpGroup = new InterpreterGroup();
intpGroup.add(new SparkInterpreter(p));
intpGroup.add(dep);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(new SparkInterpreter(p));
intpGroup.get("note").add(dep);
dep.setInterpreterGroup(intpGroup);
context = new InterpreterContext("note", "id", "title", "text", new AuthenticationInfo(),

View file

@ -24,6 +24,8 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.Properties;
import org.apache.spark.HttpServer;
import org.apache.spark.SecurityManager;
import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
import org.apache.zeppelin.display.AngularObjectRegistry;
@ -42,11 +44,11 @@ import org.slf4j.LoggerFactory;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SparkInterpreterTest {
public static SparkInterpreter repl;
public static InterpreterGroup intpGroup;
private InterpreterContext context;
private File tmpDir;
public static Logger LOGGER = LoggerFactory.getLogger(SparkInterpreterTest.class);
/**
* Get spark version number as a numerical value.
* eg. 1.1.x => 11, 1.2.x => 12, 1.3.x => 13 ...
@ -70,12 +72,14 @@ public class SparkInterpreterTest {
if (repl == null) {
Properties p = new Properties();
intpGroup = new InterpreterGroup();
intpGroup.put("note", new LinkedList<Interpreter>());
repl = new SparkInterpreter(p);
repl.setInterpreterGroup(intpGroup);
intpGroup.get("note").add(repl);
repl.open();
}
InterpreterGroup intpGroup = new InterpreterGroup();
context = new InterpreterContext("note", "id", "title", "text",
new AuthenticationInfo(),
new HashMap<String, Object>(),
@ -188,4 +192,21 @@ public class SparkInterpreterTest {
}
}
}
@Test
public void shareSingleSparkContext() throws InterruptedException {
// create another SparkInterpreter
Properties p = new Properties();
SparkInterpreter repl2 = new SparkInterpreter(p);
repl2.setInterpreterGroup(intpGroup);
intpGroup.get("note").add(repl2);
repl2.open();
assertEquals(Code.SUCCESS,
repl.interpret("print(sc.parallelize(1 to 10).count())", context).code());
assertEquals(Code.SUCCESS,
repl2.interpret("print(sc.parallelize(1 to 10).count())", context).code());
repl2.close();
}
}

View file

@ -51,17 +51,22 @@ public class SparkSqlInterpreterTest {
if (SparkInterpreterTest.repl == null) {
repl = new SparkInterpreter(p);
intpGroup = new InterpreterGroup();
repl.setInterpreterGroup(intpGroup);
repl.open();
SparkInterpreterTest.repl = repl;
SparkInterpreterTest.intpGroup = intpGroup;
} else {
repl = SparkInterpreterTest.repl;
intpGroup = SparkInterpreterTest.intpGroup;
}
sql = new SparkSqlInterpreter(p);
sql = new SparkSqlInterpreter(p);
intpGroup = new InterpreterGroup();
intpGroup.add(repl);
intpGroup.add(sql);
intpGroup = new InterpreterGroup();
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(repl);
intpGroup.get("note").add(sql);
sql.setInterpreterGroup(intpGroup);
sql.open();
}

View file

@ -1,251 +0,0 @@
/**
* 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.tachyon;
import java.io.IOException;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
import java.util.*;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tachyon.conf.TachyonConf;
import tachyon.shell.TfsShell;
/**
* Tachyon interpreter for Zeppelin.
*/
public class TachyonInterpreter extends Interpreter {
Logger logger = LoggerFactory.getLogger(TachyonInterpreter.class);
protected static final String TACHYON_MASTER_HOSTNAME = "tachyon.master.hostname";
protected static final String TACHYON_MASTER_PORT = "tachyon.master.port";
private TfsShell tfs;
private int totalCommands = 0;
private int completedCommands = 0;
private final String tachyonMasterHostname;
private final String tachyonMasterPort;
protected final List<String> keywords = Arrays.asList("cat", "copyFromLocal",
"copyToLocal", "count", "du", "fileinfo", "free", "getUsedBytes",
"getCapacityBytes", "load", "loadMetadata", "location", "ls", "lsr",
"mkdir", "mount", "mv", "pin", "report", "request", "rm", "rmr",
"setTTL", "unsetTTL", "tail", "touch", "unmount", "unpin");
public TachyonInterpreter(Properties property) {
super(property);
tachyonMasterHostname = property.getProperty(TACHYON_MASTER_HOSTNAME);
tachyonMasterPort = property.getProperty(TACHYON_MASTER_PORT);
}
static {
Interpreter.register("tachyon", "tachyon",
TachyonInterpreter.class.getName(),
new InterpreterPropertyBuilder()
.add(TACHYON_MASTER_HOSTNAME, "localhost", "Tachyon master hostname")
.add(TACHYON_MASTER_PORT, "19998", "Tachyon master port")
.build());
}
@Override
public void open() {
logger.info("Starting Tachyon shell to connect to " + tachyonMasterHostname +
" on port " + tachyonMasterPort);
System.setProperty(TACHYON_MASTER_HOSTNAME, tachyonMasterHostname);
System.setProperty(TACHYON_MASTER_PORT, tachyonMasterPort);
tfs = new TfsShell(new TachyonConf());
}
@Override
public void close() {
logger.info("Closing Tachyon shell");
try {
tfs.close();
} catch (IOException e) {
logger.error("Cannot close connection", e);
}
}
@Override
public InterpreterResult interpret(String st, InterpreterContext context) {
String[] lines = splitAndRemoveEmpty(st, "\n");
return interpret(lines, context);
}
private InterpreterResult interpret(String[] commands, InterpreterContext context) {
boolean isSuccess = true;
totalCommands = commands.length;
completedCommands = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
PrintStream old = System.out;
System.setOut(ps);
for (String command : commands) {
int commandResuld = 1;
String[] args = splitAndRemoveEmpty(command, " ");
if (args.length > 0 && args[0].equals("help")) {
System.out.println(getCommandList());
} else {
commandResuld = tfs.run(args);
}
if (commandResuld != 0) {
isSuccess = false;
break;
} else {
completedCommands += 1;
}
System.out.println();
}
System.out.flush();
System.setOut(old);
if (isSuccess) {
return new InterpreterResult(Code.SUCCESS, baos.toString());
} else {
return new InterpreterResult(Code.ERROR, baos.toString());
}
}
private String[] splitAndRemoveEmpty(String st, String splitSeparator) {
String[] voices = st.split(splitSeparator);
ArrayList<String> result = new ArrayList<String>();
for (String voice : voices) {
if (!voice.trim().isEmpty()) {
result.add(voice);
}
}
return result.toArray(new String[result.size()]);
}
private String[] splitAndRemoveEmpty(String[] sts, String splitSeparator) {
ArrayList<String> result = new ArrayList<String>();
for (String st : sts) {
result.addAll(Arrays.asList(splitAndRemoveEmpty(st, splitSeparator)));
}
return result.toArray(new String[result.size()]);
}
@Override
public void cancel(InterpreterContext context) { }
@Override
public FormType getFormType() {
return FormType.NATIVE;
}
@Override
public int getProgress(InterpreterContext context) {
return completedCommands * 100 / totalCommands;
}
@Override
public List<String> completion(String buf, int cursor) {
String[] words = splitAndRemoveEmpty(splitAndRemoveEmpty(buf, "\n"), " ");
String lastWord = "";
if (words.length > 0) {
lastWord = words[ words.length - 1 ];
}
ArrayList<String> voices = new ArrayList<String>();
for (String command : keywords) {
if (command.startsWith(lastWord)) {
voices.add(command);
}
}
return voices;
}
private String getCommandList() {
StringBuilder sb = new StringBuilder();
sb.append("Commands list:");
sb.append("\n\t[help] - List all available commands.");
sb.append("\n\t[cat <path>] - Print the content of the file to the console.");
sb.append("\n\t[copyFromLocal <src> <remoteDst>] - Copy the specified file specified " +
"by \"source path\" to the path specified by \"remote path\". " +
"This command will fail if \"remote path\" already exists.");
sb.append("\n\t[copyToLocal <src> <localDst>] - Copy the specified file from the path " +
"specified by \"remote source\" to a local destination.");
sb.append("\n\t[count <path>] - Display the number of folders and files matching " +
"the specified prefix in \"path\".");
sb.append("\n\t[du <path>] - Display the size of a file or a directory specified " +
"by the input path.");
sb.append("\n\t[fileinfo <path>] - Print the information of the blocks of a specified file.");
sb.append("\n\t[free <file path|folder path>] - Free a file or all files under a " +
"directory from Tachyon. If the file/directory is also in under storage, " +
"it will still be available there.");
sb.append("\n\t[getUsedBytes] - Get number of bytes used in the TachyonFS.");
sb.append("\n\t[getCapacityBytes] - Get the capacity of the TachyonFS.");
sb.append("\n\t[load <path>] - Load the data of a file or a directory from under " +
"storage into Tachyon.");
sb.append("\n\t[loadMetadata <path>] - Load the metadata of a file or a directory " +
"from under storage into Tachyon.");
sb.append("\n\t[location <path>] - Display a list of hosts that have the file data.");
sb.append("\n\t[ls <path>] - List all the files and directories directly under the " +
"given path with information such as size.");
sb.append("\n\t[lsr <path>] - Recursively list all the files and directories under " +
"the given path with information such as size.");
sb.append("\n\t[mkdir <path>] - Create a directory under the given path, along with " +
"any necessary parent directories. This command will fail if the given " +
"path already exists.");
sb.append("\n\t[mount <tachyonPath> <ufsURI>] - Mount the underlying file system " +
"path \"uri\" into the Tachyon namespace as \"path\". The \"path\" is assumed " +
"not to exist and is created by the operation. No data or metadata is loaded " +
"from under storage into Tachyon. After a path is mounted, operations on objects " +
"under the mounted path are mirror to the mounted under storage.");
sb.append("\n\t[mv <src> <dst>] - Move a file or directory specified by \"source\" " +
"to a new location \"destination\". This command will fail if " +
"\"destination\" already exists.");
sb.append("\n\t[pin <path>] - Pin the given file to avoid evicting it from memory. " +
"If the given path is a directory, it recursively pins all the files contained " +
"and any new files created within this directory.");
sb.append("\n\t[report <path>] - Report to the master that a file is lost.");
sb.append("\n\t[request <tachyonaddress> <dependencyId>] - Request the file for " +
"a given dependency ID.");
sb.append("\n\t[rm <path>] - Remove a file. This command will fail if the given " +
"path is a directory rather than a file.");
sb.append("\n\t[rmr <path>] - Remove a file, or a directory with all the files and " +
"sub-directories that this directory contains.");
sb.append("\n\t[tail <path>] - Print the last 1KB of the specified file to the console.");
sb.append("\n\t[touch <path>] - Create a 0-byte file at the specified location.");
sb.append("\n\t[unmount <tachyonPath>] - Unmount the underlying file system path " +
"mounted in the Tachyon namespace as \"path\". Tachyon objects under \"path\" " +
"are removed from Tachyon, but they still exist in the previously " +
"mounted under storage.");
sb.append("\n\t[unpin <path>] - Unpin the given file to allow Tachyon to evict " +
"this file again. If the given path is a directory, it recursively unpins " +
"all files contained and any new files created within this directory.");
return sb.toString();
}
}

View file

@ -1,484 +0,0 @@
/**
* 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.tachyon;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.junit.*;
import tachyon.Constants;
import tachyon.TachyonURI;
import tachyon.client.TachyonFSTestUtils;
import tachyon.client.TachyonStorageType;
import tachyon.client.UnderStorageType;
import tachyon.client.file.FileInStream;
import tachyon.client.file.TachyonFile;
import tachyon.client.file.TachyonFileSystem;
import tachyon.client.file.options.InStreamOptions;
import tachyon.conf.TachyonConf;
import tachyon.exception.ExceptionMessage;
import tachyon.exception.TachyonException;
import tachyon.master.LocalTachyonCluster;
import tachyon.shell.TfsShell;
import tachyon.thrift.FileInfo;
import tachyon.util.FormatUtils;
import tachyon.util.io.BufferUtils;
import tachyon.util.io.PathUtils;
public class TachyonInterpreterTest {
private TachyonInterpreter tachyonInterpreter;
private static final int SIZE_BYTES = Constants.MB * 10;
private LocalTachyonCluster mLocalTachyonCluster = null;
private TachyonFileSystem mTfs = null;
@After
public final void after() throws Exception {
if (tachyonInterpreter != null) {
tachyonInterpreter.close();
}
mLocalTachyonCluster.stop();
}
@Before
public final void before() throws Exception {
mLocalTachyonCluster = new LocalTachyonCluster(SIZE_BYTES, 1000, Constants.GB);
mLocalTachyonCluster.start();
mTfs = mLocalTachyonCluster.getClient();
final Properties props = new Properties();
props.put(TachyonInterpreter.TACHYON_MASTER_HOSTNAME, mLocalTachyonCluster.getMasterHostname());
props.put(TachyonInterpreter.TACHYON_MASTER_PORT, mLocalTachyonCluster.getMasterPort() + "");
tachyonInterpreter = new TachyonInterpreter(props);
tachyonInterpreter.open();
}
@Test
public void testCompletion() {
List<String> expectedResultOne = Arrays.asList("cat", "copyFromLocal",
"copyToLocal", "count");
List<String> expectedResultTwo = Arrays.asList("copyFromLocal",
"copyToLocal", "count");
List<String> expectedResultThree = Arrays.asList("copyFromLocal", "copyToLocal");
List<String> expectedResultNone = new ArrayList<String>();
List<String> resultOne = tachyonInterpreter.completion("c", 0);
List<String> resultTwo = tachyonInterpreter.completion("co", 0);
List<String> resultThree = tachyonInterpreter.completion("copy", 0);
List<String> resultNotMatch = tachyonInterpreter.completion("notMatch", 0);
List<String> resultAll = tachyonInterpreter.completion("", 0);
Assert.assertEquals(expectedResultOne, resultOne);
Assert.assertEquals(expectedResultTwo, resultTwo);
Assert.assertEquals(expectedResultThree, resultThree);
Assert.assertEquals(expectedResultNone, resultNotMatch);
Assert.assertEquals(tachyonInterpreter.keywords, resultAll);
}
@Test
public void catDirectoryTest() throws IOException {
String expected = "Successfully created directory /testDir\n\n" +
"/testDir is not a file.\n";
InterpreterResult output = tachyonInterpreter.interpret("mkdir /testDir" +
"\ncat /testDir", null);
Assert.assertEquals(Code.ERROR, output.code());
Assert.assertEquals(expected, output.message());
}
@Test
public void catNotExistTest() throws IOException {
InterpreterResult output = tachyonInterpreter.interpret("cat /testFile", null);
Assert.assertEquals(Code.ERROR, output.code());
}
@Test
public void catTest() throws IOException {
TachyonFSTestUtils.createByteFile(mTfs, "/testFile", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, 10);
InterpreterResult output = tachyonInterpreter.interpret("cat /testFile", null);
byte[] expected = BufferUtils.getIncreasingByteArray(10);
Assert.assertEquals(Code.SUCCESS, output.code());
Assert.assertArrayEquals(expected,
output.message().substring(0, output.message().length() - 1).getBytes());
}
@Test
public void copyFromLocalLargeTest() throws IOException, TachyonException {
File testFile = new File(mLocalTachyonCluster.getTachyonHome() + "/testFile");
testFile.createNewFile();
FileOutputStream fos = new FileOutputStream(testFile);
byte[] toWrite = BufferUtils.getIncreasingByteArray(SIZE_BYTES);
fos.write(toWrite);
fos.close();
InterpreterResult output = tachyonInterpreter.interpret("copyFromLocal " +
testFile.getAbsolutePath() + " /testFile", null);
Assert.assertEquals(
"Copied " + testFile.getAbsolutePath() + " to /testFile\n\n",
output.message());
TachyonFile tFile = mTfs.open(new TachyonURI("/testFile"));
FileInfo fileInfo = mTfs.getInfo(tFile);
Assert.assertNotNull(fileInfo);
Assert.assertEquals(SIZE_BYTES, fileInfo.length);
InStreamOptions options =
new InStreamOptions.Builder(new TachyonConf()).setTachyonStorageType(
TachyonStorageType.NO_STORE).build();
FileInStream tfis = mTfs.getInStream(tFile, options);
byte[] read = new byte[SIZE_BYTES];
tfis.read(read);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(SIZE_BYTES, read));
}
@Test
public void loadFileTest() throws IOException, TachyonException {
TachyonFile file =
TachyonFSTestUtils.createByteFile(mTfs, "/testFile", TachyonStorageType.NO_STORE,
UnderStorageType.SYNC_PERSIST, 10);
FileInfo fileInfo = mTfs.getInfo(file);
Assert.assertFalse(fileInfo.getInMemoryPercentage() == 100);
tachyonInterpreter.interpret("load /testFile", null);
fileInfo = mTfs.getInfo(file);
Assert.assertTrue(fileInfo.getInMemoryPercentage() == 100);
}
@Test
public void loadDirTest() throws IOException, TachyonException {
TachyonFile fileA = TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileA",
TachyonStorageType.NO_STORE, UnderStorageType.SYNC_PERSIST, 10);
TachyonFile fileB = TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileB",
TachyonStorageType.STORE, UnderStorageType.NO_PERSIST, 10);
FileInfo fileInfoA = mTfs.getInfo(fileA);
FileInfo fileInfoB = mTfs.getInfo(fileB);
Assert.assertFalse(fileInfoA.getInMemoryPercentage() == 100);
Assert.assertTrue(fileInfoB.getInMemoryPercentage() == 100);
tachyonInterpreter.interpret("load /testRoot", null);
fileInfoA = mTfs.getInfo(fileA);
fileInfoB = mTfs.getInfo(fileB);
Assert.assertTrue(fileInfoA.getInMemoryPercentage() == 100);
Assert.assertTrue(fileInfoB.getInMemoryPercentage() == 100);
}
@Test
public void copyFromLocalTest() throws IOException, TachyonException {
File testDir = new File(mLocalTachyonCluster.getTachyonHome() + "/testDir");
testDir.mkdir();
File testDirInner = new File(mLocalTachyonCluster.getTachyonHome() + "/testDir/testDirInner");
testDirInner.mkdir();
File testFile =
generateFileContent("/testDir/testFile", BufferUtils.getIncreasingByteArray(10));
generateFileContent("/testDir/testDirInner/testFile2",
BufferUtils.getIncreasingByteArray(10, 20));
InterpreterResult output = tachyonInterpreter.interpret("copyFromLocal " +
testFile.getParent() + " /testDir", null);
Assert.assertEquals(
"Copied " + testFile.getParent() + " to /testDir\n\n",
output.message());
TachyonFile file1 = mTfs.open(new TachyonURI("/testDir/testFile"));
TachyonFile file2 = mTfs.open(new TachyonURI("/testDir/testDirInner/testFile2"));
FileInfo fileInfo1 = mTfs.getInfo(file1);
FileInfo fileInfo2 = mTfs.getInfo(file2);
Assert.assertNotNull(fileInfo1);
Assert.assertNotNull(fileInfo2);
Assert.assertEquals(10, fileInfo1.length);
Assert.assertEquals(20, fileInfo2.length);
byte[] read = readContent(file1, 10);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(10, read));
read = readContent(file2, 20);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(10, 20, read));
}
@Test
public void copyFromLocalTestWithFullURI() throws IOException, TachyonException {
File testFile = generateFileContent("/srcFileURI", BufferUtils.getIncreasingByteArray(10));
String tachyonURI = "tachyon://" + mLocalTachyonCluster.getMasterHostname() + ":"
+ mLocalTachyonCluster.getMasterPort() + "/destFileURI";
InterpreterResult output = tachyonInterpreter.interpret("copyFromLocal " +
testFile.getPath() + " " + tachyonURI, null);
Assert.assertEquals(
"Copied " + testFile.getPath() + " to " + tachyonURI + "\n\n",
output.message());
TachyonFile file = mTfs.open(new TachyonURI("/destFileURI"));
FileInfo fileInfo = mTfs.getInfo(file);
Assert.assertEquals(10L, fileInfo.length);
byte[] read = readContent(file, 10);
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(10, read));
}
@Test
public void copyFromLocalFileToDstPathTest() throws IOException, TachyonException {
String dataString = "copyFromLocalFileToDstPathTest";
byte[] data = dataString.getBytes();
File localDir = new File(mLocalTachyonCluster.getTachyonHome() + "/localDir");
localDir.mkdir();
File localFile = generateFileContent("/localDir/testFile", data);
tachyonInterpreter.interpret("mkdir /dstDir", null);
tachyonInterpreter.interpret("copyFromLocal " + localFile.getPath() + " /dstDir", null);
TachyonFile file = mTfs.open(new TachyonURI("/dstDir/testFile"));
FileInfo fileInfo = mTfs.getInfo(file);
Assert.assertNotNull(fileInfo);
byte[] read = readContent(file, data.length);
Assert.assertEquals(new String(read), dataString);
}
@Test
public void copyToLocalLargeTest() throws IOException {
copyToLocalWithBytes(SIZE_BYTES);
}
@Test
public void copyToLocalTest() throws IOException {
copyToLocalWithBytes(10);
}
private void copyToLocalWithBytes(int bytes) throws IOException {
TachyonFSTestUtils.createByteFile(mTfs, "/testFile", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, bytes);
InterpreterResult output = tachyonInterpreter.interpret("copyToLocal /testFile " +
mLocalTachyonCluster.getTachyonHome() + "/testFile", null);
Assert.assertEquals(
"Copied /testFile to " + mLocalTachyonCluster.getTachyonHome() + "/testFile\n\n",
output.message());
fileReadTest("/testFile", 10);
}
@Test
public void countNotExistTest() throws IOException {
InterpreterResult output = tachyonInterpreter.interpret("count /NotExistFile", null);
Assert.assertEquals(Code.ERROR, output.code());
Assert.assertEquals(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/NotExistFile") + "\n",
output.message());
}
@Test
public void countTest() throws IOException {
TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileA", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, 10);
TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testDir/testFileB", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, 20);
TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileB", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, 30);
InterpreterResult output = tachyonInterpreter.interpret("count /testRoot", null);
String expected = "";
String format = "%-25s%-25s%-15s\n";
expected += String.format(format, "File Count", "Folder Count", "Total Bytes");
expected += String.format(format, 3, 2, 60);
expected += "\n";
Assert.assertEquals(expected, output.message());
}
@Test
public void fileinfoNotExistTest() throws IOException {
InterpreterResult output = tachyonInterpreter.interpret("fileinfo /NotExistFile", null);
Assert.assertEquals(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/NotExistFile") + "\n",
output.message());
Assert.assertEquals(Code.ERROR, output.code());
}
@Test
public void locationNotExistTest() throws IOException {
InterpreterResult output = tachyonInterpreter.interpret("location /NotExistFile", null);
Assert.assertEquals(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/NotExistFile") + "\n",
output.message());
Assert.assertEquals(Code.ERROR, output.code());
}
@Test
public void lsTest() throws IOException, TachyonException {
FileInfo[] files = new FileInfo[3];
TachyonFile fileA = TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileA",
TachyonStorageType.STORE, UnderStorageType.NO_PERSIST, 10);
files[0] = mTfs.getInfo(fileA);
TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testDir/testFileB", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, 20);
files[1] = mTfs.getInfo(mTfs.open(new TachyonURI("/testRoot/testDir")));
TachyonFile fileC = TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileC",
TachyonStorageType.NO_STORE, UnderStorageType.SYNC_PERSIST, 30);
files[2] = mTfs.getInfo(fileC);
InterpreterResult output = tachyonInterpreter.interpret("ls /testRoot", null);
String expected = "";
String format = "%-10s%-25s%-15s%-5s\n";
expected += String.format(format, FormatUtils.getSizeFromBytes(10),
TfsShell.convertMsToDate(files[0].getCreationTimeMs()), "In Memory", "/testRoot/testFileA");
expected += String.format(format, FormatUtils.getSizeFromBytes(0),
TfsShell.convertMsToDate(files[1].getCreationTimeMs()), "", "/testRoot/testDir");
expected += String.format(format, FormatUtils.getSizeFromBytes(30),
TfsShell.convertMsToDate(files[2].getCreationTimeMs()), "Not In Memory",
"/testRoot/testFileC");
expected += "\n";
Assert.assertEquals(Code.SUCCESS, output.code());
Assert.assertEquals(expected, output.message());
}
@Test
public void lsrTest() throws IOException, TachyonException {
FileInfo[] files = new FileInfo[4];
TachyonFile fileA = TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileA",
TachyonStorageType.STORE, UnderStorageType.NO_PERSIST, 10);
files[0] = mTfs.getInfo(fileA);
TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testDir/testFileB", TachyonStorageType.STORE,
UnderStorageType.NO_PERSIST, 20);
files[1] = mTfs.getInfo(mTfs.open(new TachyonURI("/testRoot/testDir")));
files[2] = mTfs.getInfo(mTfs.open(new TachyonURI("/testRoot/testDir/testFileB")));
TachyonFile fileC = TachyonFSTestUtils.createByteFile(mTfs, "/testRoot/testFileC",
TachyonStorageType.NO_STORE, UnderStorageType.SYNC_PERSIST, 30);
files[3] = mTfs.getInfo(fileC);
InterpreterResult output = tachyonInterpreter.interpret("lsr /testRoot", null);
String expected = "";
String format = "%-10s%-25s%-15s%-5s\n";
expected +=
String.format(format, FormatUtils.getSizeFromBytes(10),
TfsShell.convertMsToDate(files[0].getCreationTimeMs()), "In Memory",
"/testRoot/testFileA");
expected +=
String.format(format, FormatUtils.getSizeFromBytes(0),
TfsShell.convertMsToDate(files[1].getCreationTimeMs()), "", "/testRoot/testDir");
expected +=
String.format(format, FormatUtils.getSizeFromBytes(20),
TfsShell.convertMsToDate(files[2].getCreationTimeMs()), "In Memory",
"/testRoot/testDir/testFileB");
expected +=
String.format(format, FormatUtils.getSizeFromBytes(30),
TfsShell.convertMsToDate(files[3].getCreationTimeMs()), "Not In Memory",
"/testRoot/testFileC");
expected += "\n";
Assert.assertEquals(expected, output.message());
}
@Test
public void mkdirComplexPathTest() throws IOException, TachyonException {
InterpreterResult output = tachyonInterpreter.interpret(
"mkdir /Complex!@#$%^&*()-_=+[]{};\"'<>,.?/File", null);
TachyonFile tFile = mTfs.open(new TachyonURI("/Complex!@#$%^&*()-_=+[]{};\"'<>,.?/File"));
FileInfo fileInfo = mTfs.getInfo(tFile);
Assert.assertNotNull(fileInfo);
Assert.assertEquals(
"Successfully created directory /Complex!@#$%^&*()-_=+[]{};\"'<>,.?/File\n\n",
output.message());
Assert.assertTrue(fileInfo.isIsFolder());
}
@Test
public void mkdirExistingTest() throws IOException {
String command = "mkdir /festFile1";
Assert.assertEquals(Code.SUCCESS, tachyonInterpreter.interpret(command, null).code());
Assert.assertEquals(Code.SUCCESS, tachyonInterpreter.interpret(command, null).code());
}
@Test
public void mkdirInvalidPathTest() throws IOException {
Assert.assertEquals(
Code.ERROR,
tachyonInterpreter.interpret("mkdir /test File Invalid Path", null).code());
}
@Test
public void mkdirShortPathTest() throws IOException, TachyonException {
InterpreterResult output = tachyonInterpreter.interpret("mkdir /root/testFile1", null);
TachyonFile tFile = mTfs.open(new TachyonURI("/root/testFile1"));
FileInfo fileInfo = mTfs.getInfo(tFile);
Assert.assertNotNull(fileInfo);
Assert.assertEquals(
"Successfully created directory /root/testFile1\n\n",
output.message());
Assert.assertTrue(fileInfo.isIsFolder());
}
@Test
public void mkdirTest() throws IOException, TachyonException {
String qualifiedPath =
"tachyon://" + mLocalTachyonCluster.getMasterHostname() + ":"
+ mLocalTachyonCluster.getMasterPort() + "/root/testFile1";
InterpreterResult output = tachyonInterpreter.interpret("mkdir " + qualifiedPath, null);
TachyonFile tFile = mTfs.open(new TachyonURI("/root/testFile1"));
FileInfo fileInfo = mTfs.getInfo(tFile);
Assert.assertNotNull(fileInfo);
Assert.assertEquals(
"Successfully created directory " + qualifiedPath + "\n\n",
output.message());
Assert.assertTrue(fileInfo.isIsFolder());
}
private File generateFileContent(String path, byte[] toWrite)
throws IOException, FileNotFoundException {
File testFile = new File(mLocalTachyonCluster.getTachyonHome() + path);
testFile.createNewFile();
FileOutputStream fos = new FileOutputStream(testFile);
fos.write(toWrite);
fos.close();
return testFile;
}
private byte[] readContent(TachyonFile tFile, int length) throws IOException, TachyonException {
InStreamOptions options =
new InStreamOptions.Builder(new TachyonConf()).setTachyonStorageType(
TachyonStorageType.NO_STORE).build();
FileInStream tfis = mTfs.getInStream(tFile, options);
byte[] read = new byte[length];
tfis.read(read);
return read;
}
private void fileReadTest(String fileName, int size) throws IOException {
File testFile = new File(PathUtils.concatPath(mLocalTachyonCluster.getTachyonHome(), fileName));
FileInputStream fis = new FileInputStream(testFile);
byte[] read = new byte[size];
fis.read(read);
fis.close();
Assert.assertTrue(BufferUtils.equalIncreasingByteArray(size, read));
}
}

View file

@ -96,10 +96,10 @@ The following components are provided under Apache License.
(Apache 2.0) Shiro Web (org.apache.shiro:shiro-web:1.2.3 - https://shiro.apache.org)
(Apache 2.0) SnakeYAML (org.yaml:snakeyaml:1.15 - http://www.snakeyaml.org)
(Apache 2.0) Protocol Buffers (com.google.protobuf:protobuf-java:2.4.1 - https://github.com/google/protobuf/releases)
(Apache 2.0) Tachyon Shell (org.tachyonproject:tachyon-shell:0.8.2 - http://tachyon-project.org)
(Apache 2.0) Tachyon Servers (org.tachyonproject:tachyon-servers:0.8.2 - http://tachyon-project.org)
(Apache 2.0) Tachyon Minicluster (org.tachyonproject:tachyon-minicluster:0.8.2 - http://tachyon-project.org)
(Apache 2.0) Tachyon Underfs Local (org.tachyonproject:tachyon-underfs-local:0.8.2 - http://tachyon-project.org)
(Apache 2.0) Alluxio Shell (org.alluxio:alluxio-shell:1.0.0 - http://alluxio.org)
(Apache 2.0) Alluxio Servers (org.alluxio:alluxio-core-server:1.0.0 - http://alluxio.org)
(Apache 2.0) Alluxio Minicluster (org.alluxio:alluxio-minicluster:1.0.0 - http://alluxio.org)
(Apache 2.0) Alluxio Underfs Local (org.alluxio:alluxio-underfs-local:1.0.0 - http://alluxio.org)
(Apache 2.0) Microsoft Azure Storage Library for Java (com.microsoft.azure:azure-storage:4.0.0 - https://github.com/Azure/azure-storage-java)

View file

@ -18,7 +18,10 @@
package org.apache.zeppelin.display;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
@ -59,7 +62,7 @@ public class GUI implements Serializable {
value = defaultValue;
}
forms.put(id, new Input(id, defaultValue));
forms.put(id, new Input(id, defaultValue, "input"));
return value;
}
@ -72,10 +75,35 @@ public class GUI implements Serializable {
if (value == null) {
value = defaultValue;
}
forms.put(id, new Input(id, defaultValue, options));
forms.put(id, new Input(id, defaultValue, "select", options));
return value;
}
public Collection<Object> checkbox(String id, Collection<Object> defaultChecked,
ParamOption[] options) {
Collection<Object> checked = (Collection<Object>) params.get(id);
if (checked == null) {
checked = defaultChecked;
}
forms.put(id, new Input(id, defaultChecked, "checkbox", options));
Collection<Object> filtered = new LinkedList<Object>();
for (Object o : checked) {
if (isValidOption(o, options)) {
filtered.add(o);
}
}
return filtered;
}
private boolean isValidOption(Object o, ParamOption[] options) {
for (ParamOption option : options) {
if (o.equals(option.getValue())) {
return true;
}
}
return false;
}
public void clear() {
this.forms = new TreeMap<String, Input>();
}

View file

@ -17,8 +17,12 @@
package org.apache.zeppelin.display;
import org.apache.commons.lang.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -43,6 +47,25 @@ public class Input implements Serializable {
this.displayName = displayName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParamOption that = (ParamOption) o;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return displayName != null ? displayName.equals(that.displayName) : that.displayName == null;
}
@Override
public int hashCode() {
int result = value != null ? value.hashCode() : 0;
result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
return result;
}
public Object getValue() {
return value;
}
@ -64,29 +87,32 @@ public class Input implements Serializable {
String name;
String displayName;
String type;
String argument;
Object defaultValue;
ParamOption[] options;
boolean hidden;
public Input(String name, Object defaultValue) {
public Input(String name, Object defaultValue, String type) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.type = type;
}
public Input(String name, Object defaultValue, ParamOption[] options) {
public Input(String name, Object defaultValue, String type, ParamOption[] options) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.type = type;
this.options = options;
}
public Input(String name, String displayName, String type, Object defaultValue,
public Input(String name, String displayName, String type, String argument, Object defaultValue,
ParamOption[] options, boolean hidden) {
super();
this.name = name;
this.displayName = displayName;
this.argument = argument;
this.type = type;
this.defaultValue = defaultValue;
this.options = options;
@ -142,6 +168,18 @@ public class Input implements Serializable {
return hidden;
}
// Syntax of variables: ${TYPE:NAME=DEFAULT_VALUE1|DEFAULT_VALUE2|...,VALUE1|VALUE2|...}
// Type is optional. Type may contain an optional argument with syntax: TYPE(ARG)
// NAME and VALUEs may contain an optional display name with syntax: NAME(DISPLAY_NAME)
// DEFAULT_VALUEs may not contain display name
// Examples: ${age} input form without default value
// ${age=3} input form with default value
// ${age(Age)=3} input form with display name and default value
// ${country=US(United States)|UK|JP} select form with
// ${checkbox( or ):country(Country)=US|JP,US(United States)|UK|JP}
// checkbox form with " or " as delimiter: will be
// expanded to "US or JP"
private static final Pattern VAR_PTN = Pattern.compile("([_])?[$][{]([^=}]*([=][^}]*)?)[}]");
private static String[] getNameAndDisplayName(String str) {
Pattern p = Pattern.compile("([^(]*)\\s*[(]([^)]*)[)]");
@ -156,17 +194,103 @@ public class Input implements Serializable {
}
private static String[] getType(String str) {
Pattern p = Pattern.compile("([^:]*)\\s*:\\s*(.*)");
Pattern p = Pattern.compile("([^:()]*)\\s*([(][^()]*[)])?\\s*:(.*)");
Matcher m = p.matcher(str.trim());
if (m == null || m.find() == false) {
return null;
}
String[] ret = new String[2];
String[] ret = new String[3];
ret[0] = m.group(1).trim();
ret[1] = m.group(2).trim();
if (m.group(2) != null) {
ret[1] = m.group(2).trim().replaceAll("[()]", "");
}
ret[2] = m.group(3).trim();
return ret;
}
private static Input getInputForm(Matcher match) {
String hiddenPart = match.group(1);
boolean hidden = false;
if ("_".equals(hiddenPart)) {
hidden = true;
}
String m = match.group(2);
String namePart;
String valuePart;
int p = m.indexOf('=');
if (p > 0) {
namePart = m.substring(0, p);
valuePart = m.substring(p + 1);
} else {
namePart = m;
valuePart = null;
}
String varName;
String displayName = null;
String type = null;
String arg = null;
Object defaultValue = "";
ParamOption[] paramOptions = null;
// get var name type
String varNamePart;
String[] typeArray = getType(namePart);
if (typeArray != null) {
type = typeArray[0];
arg = typeArray[1];
varNamePart = typeArray[2];
} else {
varNamePart = namePart;
}
// get var name and displayname
String[] varNameArray = getNameAndDisplayName(varNamePart);
if (varNameArray != null) {
varName = varNameArray[0];
displayName = varNameArray[1];
} else {
varName = varNamePart.trim();
}
// get defaultValue
if (valuePart != null) {
// find default value
int optionP = valuePart.indexOf(",");
if (optionP >= 0) { // option available
defaultValue = valuePart.substring(0, optionP);
if (type != null && type.equals("checkbox")) {
// checkbox may contain multiple default checks
defaultValue = Input.splitPipe((String) defaultValue);
}
String optionPart = valuePart.substring(optionP + 1);
String[] options = Input.splitPipe(optionPart);
paramOptions = new ParamOption[options.length];
for (int i = 0; i < options.length; i++) {
String[] optNameArray = getNameAndDisplayName(options[i]);
if (optNameArray != null) {
paramOptions[i] = new ParamOption(optNameArray[0], optNameArray[1]);
} else {
paramOptions[i] = new ParamOption(options[i], null);
}
}
} else { // no option
defaultValue = valuePart;
}
}
return new Input(varName, displayName, type, arg, defaultValue, paramOptions, hidden);
}
public static Map<String, Input> extractSimpleQueryParam(String script) {
Map<String, Input> params = new HashMap<String, Input>();
if (script == null) {
@ -174,122 +298,57 @@ public class Input implements Serializable {
}
String replaced = script;
Pattern pattern = Pattern.compile("([_])?[$][{]([^=}]*([=][^}]*)?)[}]");
Matcher match = pattern.matcher(replaced);
Matcher match = VAR_PTN.matcher(replaced);
while (match.find()) {
String hiddenPart = match.group(1);
boolean hidden = false;
if ("_".equals(hiddenPart)) {
hidden = true;
}
String m = match.group(2);
String namePart;
String valuePart;
int p = m.indexOf('=');
if (p > 0) {
namePart = m.substring(0, p);
valuePart = m.substring(p + 1);
} else {
namePart = m;
valuePart = null;
}
String varName;
String displayName = null;
String type = null;
String defaultValue = "";
ParamOption[] paramOptions = null;
// get var name type
String varNamePart;
String[] typeArray = getType(namePart);
if (typeArray != null) {
type = typeArray[0];
varNamePart = typeArray[1];
} else {
varNamePart = namePart;
}
// get var name and displayname
String[] varNameArray = getNameAndDisplayName(varNamePart);
if (varNameArray != null) {
varName = varNameArray[0];
displayName = varNameArray[1];
} else {
varName = varNamePart.trim();
}
// get defaultValue
if (valuePart != null) {
// find default value
int optionP = valuePart.indexOf(",");
if (optionP > 0) { // option available
defaultValue = valuePart.substring(0, optionP);
String optionPart = valuePart.substring(optionP + 1);
String[] options = Input.splitPipe(optionPart);
paramOptions = new ParamOption[options.length];
for (int i = 0; i < options.length; i++) {
String[] optNameArray = getNameAndDisplayName(options[i]);
if (optNameArray != null) {
paramOptions[i] = new ParamOption(optNameArray[0], optNameArray[1]);
} else {
paramOptions[i] = new ParamOption(options[i], null);
}
}
} else { // no option
defaultValue = valuePart;
}
}
Input param = new Input(varName, displayName, type, defaultValue, paramOptions, hidden);
params.put(varName, param);
Input param = getInputForm(match);
params.put(param.name, param);
}
params.remove("pql");
return params;
}
private static final String DEFAULT_DELIMITER = ",";
public static String getSimpleQuery(Map<String, Object> params, String script) {
String replaced = script;
for (String key : params.keySet()) {
Object value = params.get(key);
replaced =
replaced.replaceAll("[_]?[$][{]([^:]*[:])?" + key + "([(][^)]*[)])?(=[^}]*)?[}]",
value.toString());
}
Pattern pattern = Pattern.compile("[$][{]([^=}]*[=][^}]*)[}]");
while (true) {
Matcher match = pattern.matcher(replaced);
if (match != null && match.find()) {
String m = match.group(1);
int p = m.indexOf('=');
String replacement = m.substring(p + 1);
int optionP = replacement.indexOf(",");
if (optionP > 0) {
replacement = replacement.substring(0, optionP);
}
replaced =
replaced.replaceFirst("[_]?[$][{]"
+ m.replaceAll("[(]", ".").replaceAll("[)]", ".").replaceAll("[|]", ".") + "[}]",
replacement);
Matcher match = VAR_PTN.matcher(replaced);
while (match.find()) {
Input input = getInputForm(match);
Object value;
if (params.containsKey(input.name)) {
value = params.get(input.name);
} else {
break;
value = input.defaultValue;
}
String expanded;
if (value instanceof Object[] || value instanceof Collection) { // multi-selection
String delimiter = input.argument;
if (delimiter == null) {
delimiter = DEFAULT_DELIMITER;
}
Collection<Object> checked = value instanceof Collection ? (Collection<Object>) value
: Arrays.asList((Object[]) value);
List<Object> validChecked = new LinkedList<Object>();
for (Object o : checked) { // filter out obsolete checked values
for (ParamOption option : input.getOptions()) {
if (option.getValue().equals(o)) {
validChecked.add(o);
break;
}
}
}
params.put(input.name, validChecked);
expanded = StringUtils.join(validChecked, delimiter);
} else { // single-selection
expanded = value.toString();
}
replaced = match.replaceFirst(expanded);
match = VAR_PTN.matcher(replaced);
}
replaced = replaced.replace("[_]?[$][{]([^=}]*)[}]", "");
return replaced;
}

View file

@ -121,10 +121,6 @@ public abstract class Interpreter {
* Called when interpreter is no longer used.
*/
public void destroy() {
Scheduler scheduler = getScheduler();
if (scheduler != null) {
scheduler.stop();
}
}
public static Logger logger = LoggerFactory.getLogger(Interpreter.class);
@ -193,6 +189,33 @@ public abstract class Interpreter {
this.classloaderUrls = classloaderUrls;
}
public Interpreter getInterpreterInTheSameSessionByClassName(String className) {
synchronized (interpreterGroup) {
for (List<Interpreter> interpreters : interpreterGroup.values()) {
boolean belongsToSameNoteGroup = false;
Interpreter interpreterFound = null;
for (Interpreter intp : interpreters) {
if (intp.getClassName().equals(className)) {
interpreterFound = intp;
}
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
if (this == p) {
belongsToSameNoteGroup = true;
}
}
if (belongsToSameNoteGroup) {
return interpreterFound;
}
}
}
return null;
}
/**
* Type of interpreter.

View file

@ -23,13 +23,24 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcess;
import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterService;
import org.apache.zeppelin.resource.ResourcePool;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.scheduler.SchedulerFactory;
/**
* InterpreterGroup is list of interpreters in the same group.
* InterpreterGroup is list of interpreters in the same interpreter group.
* For example spark, pyspark, sql interpreters are in the same 'spark' group
* and InterpreterGroup will have reference to these all interpreters.
*
* Remember, list of interpreters are dedicated to a note.
* (when InterpreterOption.perNoteSession==true)
* So InterpreterGroup internally manages map of [noteId, list of interpreters]
*
* A InterpreterGroup runs on interpreter process.
* And unit of interpreter instantiate, restart, bind, unbind.
*/
public class InterpreterGroup extends LinkedList<Interpreter>{
public class InterpreterGroup extends ConcurrentHashMap<String, List<Interpreter>> {
String id;
Logger LOGGER = Logger.getLogger(InterpreterGroup.class);
@ -38,10 +49,14 @@ public class InterpreterGroup extends LinkedList<Interpreter>{
RemoteInterpreterProcess remoteInterpreterProcess; // attached remote interpreter process
ResourcePool resourcePool;
// map [notebook session, Interpreters in the group], to support per note session interpreters
//Map<String, List<Interpreter>> interpreters = new ConcurrentHashMap<String,
// List<Interpreter>>();
private static final Map<String, InterpreterGroup> allInterpreterGroups =
new ConcurrentHashMap<String, InterpreterGroup>();
public static InterpreterGroup get(String id) {
public static InterpreterGroup getByInterpreterGroupId(String id) {
return allInterpreterGroups.get(id);
}
@ -49,11 +64,18 @@ public class InterpreterGroup extends LinkedList<Interpreter>{
return new LinkedList(allInterpreterGroups.values());
}
/**
* Create InterpreterGroup with given id
* @param id
*/
public InterpreterGroup(String id) {
this.id = id;
allInterpreterGroups.put(id, this);
}
/**
* Create InterpreterGroup with autogenerated id
*/
public InterpreterGroup() {
getId();
allInterpreterGroups.put(id, this);
@ -73,10 +95,22 @@ public class InterpreterGroup extends LinkedList<Interpreter>{
}
}
/**
* Get combined property of all interpreters in this group
* @return
*/
public Properties getProperty() {
Properties p = new Properties();
for (Interpreter intp : this) {
p.putAll(intp.getProperty());
Collection<List<Interpreter>> intpGroupForANote = this.values();
if (intpGroupForANote != null && intpGroupForANote.size() > 0) {
for (List<Interpreter> intpGroup : intpGroupForANote) {
for (Interpreter intp : intpGroup) {
p.putAll(intp.getProperty());
}
// it's okay to break here while every List<Interpreters> will have the same property set
break;
}
}
return p;
}
@ -97,13 +131,45 @@ public class InterpreterGroup extends LinkedList<Interpreter>{
this.remoteInterpreterProcess = remoteInterpreterProcess;
}
/**
* Close all interpreter instances in this group
*/
public void close() {
LOGGER.info("Close interpreter group " + getId());
List<Interpreter> intpToClose = new LinkedList<Interpreter>();
for (List<Interpreter> intpGroupForNote : this.values()) {
intpToClose.addAll(intpGroupForNote);
}
close(intpToClose);
}
/**
* Close all interpreter instances in this group for the note
* @param noteId
*/
public void close(String noteId) {
LOGGER.info("Close interpreter group " + getId() + " for note " + noteId);
List<Interpreter> intpForNote = this.get(noteId);
close(intpForNote);
}
private void close(Collection<Interpreter> intpToClose) {
if (intpToClose == null) {
return;
}
List<Thread> closeThreads = new LinkedList<Thread>();
for (final Interpreter intp : this) {
for (final Interpreter intp : intpToClose) {
Thread t = new Thread() {
public void run() {
Scheduler scheduler = intp.getScheduler();
intp.close();
if (scheduler != null) {
SchedulerFactory.singleton().removeScheduler(scheduler.getName());
}
}
};
@ -120,10 +186,46 @@ public class InterpreterGroup extends LinkedList<Interpreter>{
}
}
/**
* Destroy all interpreter instances in this group for the note
* @param noteId
*/
public void destroy(String noteId) {
LOGGER.info("Destroy interpreter group " + getId() + " for note " + noteId);
List<Interpreter> intpForNote = this.get(noteId);
destroy(intpForNote);
}
/**
* Destroy all interpreter instances in this group
*/
public void destroy() {
LOGGER.info("Destroy interpreter group " + getId());
List<Interpreter> intpToDestroy = new LinkedList<Interpreter>();
for (List<Interpreter> intpGroupForNote : this.values()) {
intpToDestroy.addAll(intpGroupForNote);
}
destroy(intpToDestroy);
// make sure remote interpreter process terminates
if (remoteInterpreterProcess != null) {
while (remoteInterpreterProcess.referenceCount() > 0) {
remoteInterpreterProcess.dereference();
}
}
allInterpreterGroups.remove(id);
}
private void destroy(Collection<Interpreter> intpToDestroy) {
if (intpToDestroy == null) {
return;
}
List<Thread> destroyThreads = new LinkedList<Thread>();
for (final Interpreter intp : this) {
for (final Interpreter intp : intpToDestroy) {
Thread t = new Thread() {
public void run() {
intp.destroy();
@ -141,17 +243,10 @@ public class InterpreterGroup extends LinkedList<Interpreter>{
LOGGER.error("Can't close interpreter", e);
}
}
// make sure remote interpreter process terminates
if (remoteInterpreterProcess != null) {
while (remoteInterpreterProcess.referenceCount() > 0) {
remoteInterpreterProcess.dereference();
}
}
allInterpreterGroups.remove(id);
}
public void setResourcePool(ResourcePool resourcePool) {
this.resourcePool = resourcePool;
}

View file

@ -19,20 +19,20 @@ package org.apache.zeppelin.interpreter.remote;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectListener;
import org.apache.zeppelin.interpreter.InterpreterGroup;
/**
* Proxy for AngularObject that exists in remote interpreter process
*/
public class RemoteAngularObject extends AngularObject {
private transient RemoteInterpreterProcess remoteInterpreterProcess;
private transient InterpreterGroup interpreterGroup;
RemoteAngularObject(String name, Object o, String noteId, String paragraphId, String
interpreterGroupId,
AngularObjectListener listener,
RemoteInterpreterProcess remoteInterpreterProcess) {
RemoteAngularObject(String name, Object o, String noteId, String paragraphId,
InterpreterGroup interpreterGroup,
AngularObjectListener listener) {
super(name, o, noteId, paragraphId, listener);
this.remoteInterpreterProcess = remoteInterpreterProcess;
this.interpreterGroup = interpreterGroup;
}
@Override
@ -45,8 +45,9 @@ public class RemoteAngularObject extends AngularObject {
if (emitRemoteProcess) {
// send updated value to remote interpreter
remoteInterpreterProcess.updateRemoteAngularObject(getName(), getNoteId(), getParagraphId()
, o);
interpreterGroup.getRemoteInterpreterProcess().
updateRemoteAngularObject(
getName(), getNoteId(), getParagraphId(), o);
}
}
}

View file

@ -47,19 +47,7 @@ public class RemoteAngularObjectRegistry extends AngularObjectRegistry {
}
private RemoteInterpreterProcess getRemoteInterpreterProcess() {
if (interpreterGroup.size() == 0) {
throw new RuntimeException("Can't get remoteInterpreterProcess");
}
Interpreter p = interpreterGroup.get(0);
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
if (p instanceof RemoteInterpreter) {
return ((RemoteInterpreter) p).getInterpreterProcess();
} else {
throw new RuntimeException("Can't get remoteInterpreterProcess");
}
return interpreterGroup.getRemoteInterpreterProcess();
}
/**
@ -141,12 +129,7 @@ public class RemoteAngularObjectRegistry extends AngularObjectRegistry {
@Override
protected AngularObject createNewAngularObject(String name, Object o, String noteId, String
paragraphId) {
RemoteInterpreterProcess remoteInterpreterProcess = getRemoteInterpreterProcess();
if (remoteInterpreterProcess == null) {
throw new RuntimeException("Remote Interpreter process not found");
}
return new RemoteAngularObject(name, o, noteId, paragraphId, getInterpreterGroupId(),
getAngularObjectListener(),
getRemoteInterpreterProcess());
return new RemoteAngularObject(name, o, noteId, paragraphId, interpreterGroup,
getAngularObjectListener());
}
}

View file

@ -17,19 +17,11 @@
package org.apache.zeppelin.interpreter.remote;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.*;
import org.apache.thrift.TException;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.*;
import org.apache.zeppelin.interpreter.InterpreterResult.Type;
import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterContext;
import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterResult;
@ -43,7 +35,7 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
*
* Proxy for Interpreter instance that runs on separate process
*/
public class RemoteInterpreter extends Interpreter {
private final RemoteInterpreterProcessListener remoteInterpreterProcessListener;
@ -53,13 +45,16 @@ public class RemoteInterpreter extends Interpreter {
private String interpreterPath;
private String localRepoPath;
private String className;
private String noteId;
FormType formType;
boolean initialized;
private Map<String, String> env;
private int connectTimeout;
private int maxPoolSize;
private static String schedulerName;
public RemoteInterpreter(Properties property,
String noteId,
String className,
String interpreterRunner,
String interpreterPath,
@ -68,6 +63,7 @@ public class RemoteInterpreter extends Interpreter {
int maxPoolSize,
RemoteInterpreterProcessListener remoteInterpreterProcessListener) {
super(property);
this.noteId = noteId;
this.className = className;
initialized = false;
this.interpreterRunner = interpreterRunner;
@ -80,6 +76,7 @@ public class RemoteInterpreter extends Interpreter {
}
public RemoteInterpreter(Properties property,
String noteId,
String className,
String interpreterRunner,
String interpreterPath,
@ -89,6 +86,7 @@ public class RemoteInterpreter extends Interpreter {
RemoteInterpreterProcessListener remoteInterpreterProcessListener) {
super(property);
this.className = className;
this.noteId = noteId;
this.interpreterRunner = interpreterRunner;
this.interpreterPath = interpreterPath;
this.localRepoPath = localRepoPath;
@ -123,39 +121,37 @@ public class RemoteInterpreter extends Interpreter {
}
}
private synchronized void init() {
public synchronized void init() {
if (initialized == true) {
return;
}
RemoteInterpreterProcess interpreterProcess = getInterpreterProcess();
int rc = interpreterProcess.reference(getInterpreterGroup());
interpreterProcess.setMaxPoolSize(this.maxPoolSize);
synchronized (interpreterProcess) {
// when first process created
if (rc == 1) {
// create all interpreter class in this interpreter group
Client client = null;
try {
client = interpreterProcess.getClient();
} catch (Exception e1) {
throw new InterpreterException(e1);
}
boolean broken = false;
try {
for (Interpreter intp : this.getInterpreterGroup()) {
logger.info("Create remote interpreter {}", intp.getClassName());
property.put("zeppelin.interpreter.localRepo", localRepoPath);
client.createInterpreter(getInterpreterGroup().getId(),
intp.getClassName(), (Map) property);
}
} catch (TException e) {
broken = true;
throw new InterpreterException(e);
} finally {
interpreterProcess.releaseClient(client, broken);
}
interpreterProcess.reference(getInterpreterGroup());
interpreterProcess.setMaxPoolSize(
Math.max(this.maxPoolSize, interpreterProcess.getMaxPoolSize()));
String groupId = getInterpreterGroup().getId();
synchronized (interpreterProcess) {
Client client = null;
try {
client = interpreterProcess.getClient();
} catch (Exception e1) {
throw new InterpreterException(e1);
}
boolean broken = false;
try {
logger.info("Create remote interpreter {}", getClassName());
property.put("zeppelin.interpreter.localRepo", localRepoPath);
client.createInterpreter(groupId, noteId,
getClassName(), (Map) property);
} catch (TException e) {
broken = true;
throw new InterpreterException(e);
} finally {
interpreterProcess.releaseClient(client, broken);
}
}
initialized = true;
@ -165,19 +161,31 @@ public class RemoteInterpreter extends Interpreter {
@Override
public void open() {
init();
InterpreterGroup interpreterGroup = getInterpreterGroup();
synchronized (interpreterGroup) {
// initialize all interpreters in this interpreter group
List<Interpreter> interpreters = interpreterGroup.get(noteId);
for (Interpreter intp : interpreters) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
((RemoteInterpreter) p).init();
}
}
}
@Override
public void close() {
RemoteInterpreterProcess interpreterProcess = getInterpreterProcess();
Client client = null;
Client client = null;
boolean broken = false;
try {
client = interpreterProcess.getClient();
if (client != null) {
client.close(className);
client.close(noteId, className);
}
} catch (TException e) {
broken = true;
@ -219,7 +227,8 @@ public class RemoteInterpreter extends Interpreter {
boolean broken = false;
try {
GUI settings = context.getGui();
RemoteInterpreterResult remoteResult = client.interpret(className, st, convert(context));
RemoteInterpreterResult remoteResult = client.interpret(
noteId, className, st, convert(context));
Map<String, Object> remoteConfig = (Map<String, Object>) gson.fromJson(
remoteResult.getConfig(), new TypeToken<Map<String, Object>>() {
@ -256,7 +265,7 @@ public class RemoteInterpreter extends Interpreter {
boolean broken = false;
try {
client.cancel(className, convert(context));
client.cancel(noteId, className, convert(context));
} catch (TException e) {
broken = true;
throw new InterpreterException(e);
@ -284,7 +293,7 @@ public class RemoteInterpreter extends Interpreter {
boolean broken = false;
try {
formType = FormType.valueOf(client.getFormType(className));
formType = FormType.valueOf(client.getFormType(noteId, className));
return formType;
} catch (TException e) {
broken = true;
@ -310,7 +319,7 @@ public class RemoteInterpreter extends Interpreter {
boolean broken = false;
try {
return client.getProgress(className, convert(context));
return client.getProgress(noteId, className, convert(context));
} catch (TException e) {
broken = true;
throw new InterpreterException(e);
@ -332,7 +341,7 @@ public class RemoteInterpreter extends Interpreter {
boolean broken = false;
try {
return client.completion(className, buf, cursor);
return client.completion(noteId, className, buf, cursor);
} catch (TException e) {
broken = true;
throw new InterpreterException(e);
@ -349,7 +358,9 @@ public class RemoteInterpreter extends Interpreter {
return null;
} else {
return SchedulerFactory.singleton().createOrGetRemoteScheduler(
"remoteinterpreter_" + interpreterProcess.hashCode(), interpreterProcess,
RemoteInterpreter.class.getName() + noteId + interpreterProcess.hashCode(),
noteId,
interpreterProcess,
maxConcurrency);
}
}

View file

@ -244,7 +244,8 @@ public class RemoteInterpreterEventPoller extends Thread {
}
private Object getResource(ResourceId resourceId) {
InterpreterGroup intpGroup = InterpreterGroup.get(resourceId.getResourcePoolId());
InterpreterGroup intpGroup = InterpreterGroup.getByInterpreterGroupId(
resourceId.getResourcePoolId());
if (intpGroup == null) {
return null;
}

View file

@ -281,6 +281,15 @@ public class RemoteInterpreterProcess implements ExecuteResultHandler {
clientPool.setMaxTotal(size + 2);
}
}
public int getMaxPoolSize() {
if (clientPool != null) {
return clientPool.getMaxTotal();
} else {
return 0;
}
}
/**
* Called when angular object is updated in client side to propagate
* change to the remote process

View file

@ -23,11 +23,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.*;
import org.apache.thrift.TException;
import org.apache.thrift.server.TThreadPoolServer;
@ -140,10 +136,9 @@ public class RemoteInterpreterServer
@Override
public void createInterpreter(String interpreterGroupId, String className, Map<String, String>
properties)
throws TException {
public void createInterpreter(String interpreterGroupId, String noteId, String
className,
Map<String, String> properties) throws TException {
if (interpreterGroup == null) {
interpreterGroup = new InterpreterGroup(interpreterGroupId);
angularObjectRegistry = new AngularObjectRegistry(interpreterGroup.getId(), this);
@ -165,8 +160,13 @@ public class RemoteInterpreterServer
repl.setClassloaderUrls(new URL[]{});
synchronized (interpreterGroup) {
interpreterGroup.add(new LazyOpenInterpreter(
new ClassloaderInterpreter(repl, cl)));
List<Interpreter> interpreters = interpreterGroup.get(noteId);
if (interpreters == null) {
interpreters = new LinkedList<Interpreter>();
interpreterGroup.put(noteId, interpreters);
}
interpreters.add(new LazyOpenInterpreter(new ClassloaderInterpreter(repl, cl)));
}
logger.info("Instantiate interpreter {}", className);
@ -179,9 +179,18 @@ public class RemoteInterpreterServer
}
}
private Interpreter getInterpreter(String className) throws TException {
private Interpreter getInterpreter(String noteId, String className) throws TException {
if (interpreterGroup == null) {
throw new TException(
new InterpreterException("Interpreter instance " + className + " not created"));
}
synchronized (interpreterGroup) {
for (Interpreter inp : interpreterGroup) {
List<Interpreter> interpreters = interpreterGroup.get(noteId);
if (interpreters == null) {
throw new TException(
new InterpreterException("Interpreter " + className + " not initialized"));
}
for (Interpreter inp : interpreters) {
if (inp.getClassName().equals(className)) {
return inp;
}
@ -192,23 +201,35 @@ public class RemoteInterpreterServer
}
@Override
public void open(String className) throws TException {
Interpreter intp = getInterpreter(className);
public void open(String noteId, String className) throws TException {
Interpreter intp = getInterpreter(noteId, className);
intp.open();
}
@Override
public void close(String className) throws TException {
Interpreter intp = getInterpreter(className);
intp.close();
public void close(String noteId, String className) throws TException {
synchronized (interpreterGroup) {
List<Interpreter> interpreters = interpreterGroup.get(noteId);
if (interpreters != null) {
Iterator<Interpreter> it = interpreters.iterator();
while (it.hasNext()) {
Interpreter inp = it.next();
if (inp.getClassName().equals(className)) {
inp.close();
it.remove();
break;
}
}
}
}
}
@Override
public RemoteInterpreterResult interpret(String className, String st,
public RemoteInterpreterResult interpret(String noteId, String className, String st,
RemoteInterpreterContext interpreterContext) throws TException {
logger.debug("st: {}", st);
Interpreter intp = getInterpreter(className);
Interpreter intp = getInterpreter(noteId, className);
InterpreterContext context = convert(interpreterContext);
Scheduler scheduler = intp.getScheduler();
@ -341,10 +362,10 @@ public class RemoteInterpreterServer
@Override
public void cancel(String className, RemoteInterpreterContext interpreterContext)
public void cancel(String noteId, String className, RemoteInterpreterContext interpreterContext)
throws TException {
logger.info("cancel {} {}", className, interpreterContext.getParagraphId());
Interpreter intp = getInterpreter(className);
Interpreter intp = getInterpreter(noteId, className);
String jobId = interpreterContext.getParagraphId();
Job job = intp.getScheduler().removeFromWaitingQueue(jobId);
@ -356,22 +377,24 @@ public class RemoteInterpreterServer
}
@Override
public int getProgress(String className, RemoteInterpreterContext interpreterContext)
public int getProgress(String noteId, String className,
RemoteInterpreterContext interpreterContext)
throws TException {
Interpreter intp = getInterpreter(className);
Interpreter intp = getInterpreter(noteId, className);
return intp.getProgress(convert(interpreterContext));
}
@Override
public String getFormType(String className) throws TException {
Interpreter intp = getInterpreter(className);
public String getFormType(String noteId, String className) throws TException {
Interpreter intp = getInterpreter(noteId, className);
return intp.getFormType().toString();
}
@Override
public List<String> completion(String className, String buf, int cursor) throws TException {
Interpreter intp = getInterpreter(className);
public List<String> completion(String noteId, String className, String buf, int cursor)
throws TException {
Interpreter intp = getInterpreter(noteId, className);
return intp.completion(buf, cursor);
}
@ -441,14 +464,19 @@ public class RemoteInterpreterServer
}
@Override
public String getStatus(String jobId)
public String getStatus(String noteId, String jobId)
throws TException {
if (interpreterGroup == null) {
return "Unknown";
}
synchronized (interpreterGroup) {
for (Interpreter intp : interpreterGroup) {
List<Interpreter> interpreters = interpreterGroup.get(noteId);
if (interpreters == null) {
return "Unknown";
}
for (Interpreter intp : interpreters) {
for (Job job : intp.getScheduler().getJobsRunning()) {
if (jobId.equals(job.getId())) {
return job.getStatus().name();

View file

@ -45,14 +45,16 @@ public class RemoteScheduler implements Scheduler {
boolean terminate = false;
private String name;
private int maxConcurrency;
private final String noteId;
private RemoteInterpreterProcess interpreterProcess;
public RemoteScheduler(String name, ExecutorService executor,
public RemoteScheduler(String name, ExecutorService executor, String noteId,
RemoteInterpreterProcess interpreterProcess, SchedulerListener listener,
int maxConcurrency) {
this.name = name;
this.executor = executor;
this.listener = listener;
this.noteId = noteId;
this.interpreterProcess = interpreterProcess;
this.maxConcurrency = maxConcurrency;
}
@ -257,7 +259,7 @@ public class RemoteScheduler implements Scheduler {
boolean broken = false;
try {
String statusStr = client.getStatus(job.getId());
String statusStr = client.getStatus(noteId, job.getId());
if ("Unknown".equals(statusStr)) {
// not found this job in the remote schedulers.
// maybe not submitted, maybe already finished

View file

@ -86,6 +86,7 @@ public class SchedulerFactory implements SchedulerListener {
public Scheduler createOrGetRemoteScheduler(
String name,
String noteId,
RemoteInterpreterProcess interpreterProcess,
int maxConcurrency) {
@ -94,6 +95,7 @@ public class SchedulerFactory implements SchedulerListener {
Scheduler s = new RemoteScheduler(
name,
executor,
noteId,
interpreterProcess,
this,
maxConcurrency);

View file

@ -56,18 +56,18 @@ struct RemoteInterpreterEvent {
}
service RemoteInterpreterService {
void createInterpreter(1: string intpGroupId, 2: string className, 3: map<string, string> properties);
void createInterpreter(1: string intpGroupId, 2: string noteId, 3: string className, 4: map<string, string> properties);
void open(1: string className);
void close(1: string className);
RemoteInterpreterResult interpret(1: string className, 2: string st, 3: RemoteInterpreterContext interpreterContext);
void cancel(1: string className, 2: RemoteInterpreterContext interpreterContext);
i32 getProgress(1: string className, 2: RemoteInterpreterContext interpreterContext);
string getFormType(1: string className);
list<string> completion(1: string className, 2: string buf, 3: i32 cursor);
void open(1: string noteId, 2: string className);
void close(1: string noteId, 2: string className);
RemoteInterpreterResult interpret(1: string noteId, 2: string className, 3: string st, 4: RemoteInterpreterContext interpreterContext);
void cancel(1: string noteId, 2: string className, 3: RemoteInterpreterContext interpreterContext);
i32 getProgress(1: string noteId, 2: string className, 3: RemoteInterpreterContext interpreterContext);
string getFormType(1: string noteId, 2: string className);
list<string> completion(1: string noteId, 2: string className, 3: string buf, 4: i32 cursor);
void shutdown();
string getStatus(1:string jobId);
string getStatus(1: string noteId, 2:string jobId);
RemoteInterpreterEvent getEvent();

View file

@ -17,12 +17,19 @@
package org.apache.zeppelin.display;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.apache.zeppelin.display.Input.ParamOption;
public class InputTest {
@Before
@ -34,6 +41,88 @@ public class InputTest {
}
@Test
public void testDefaultParamReplace() throws IOException{
public void testFormExtraction() {
// input form
String script = "${input_form=}";
Map<String, Input> forms = Input.extractSimpleQueryParam(script);
assertEquals(1, forms.size());
Input form = forms.get("input_form");
assertEquals("input_form", form.name);
assertNull(form.displayName);
assertEquals("", form.defaultValue);
assertNull(form.options);
// input form with display name & default value
script = "${input_form(Input Form)=xxx}";
forms = Input.extractSimpleQueryParam(script);
form = forms.get("input_form");
assertEquals("xxx", form.defaultValue);
// selection form
script = "${select_form(Selection Form)=op1,op1|op2(Option 2)|op3}";
form = Input.extractSimpleQueryParam(script).get("select_form");
assertEquals("select_form", form.name);
assertEquals("op1", form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
new ParamOption("op2", "Option 2"), new ParamOption("op3", null)}, form.options);
// checkbox form
script = "${checkbox:checkbox_form=op1,op1|op2|op3}";
form = Input.extractSimpleQueryParam(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("checkbox", form.type);
assertArrayEquals(new Object[]{"op1"}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
// checkbox form with multiple default checks
script = "${checkbox:checkbox_form(Checkbox Form)=op1|op3,op1(Option 1)|op2|op3}";
form = Input.extractSimpleQueryParam(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("Checkbox Form", form.displayName);
assertEquals("checkbox", form.type);
assertArrayEquals(new Object[]{"op1", "op3"}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", "Option 1"),
new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
// checkbox form with no default check
script = "${checkbox:checkbox_form(Checkbox Form)=,op1(Option 1)|op2(Option 2)|op3(Option 3)}";
form = Input.extractSimpleQueryParam(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("Checkbox Form", form.displayName);
assertEquals("checkbox", form.type);
assertArrayEquals(new Object[]{}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", "Option 1"),
new ParamOption("op2", "Option 2"), new ParamOption("op3", "Option 3")}, form.options);
}
@Test
public void testFormSubstitution() {
// test form substitution without new forms
String script = "INPUT=${input_form=}SELECTED=${select_form(Selection Form)=,s_op1|s_op2|s_op3}\n" +
"CHECKED=${checkbox:checkbox_form=c_op1|c_op2,c_op1|c_op2|c_op3}";
Map<String, Object> params = new HashMap<String, Object>();
params.put("input_form", "some_input");
params.put("select_form", "s_op2");
params.put("checkbox_form", new String[]{"c_op1", "c_op3"});
String replaced = Input.getSimpleQuery(params, script);
assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1,c_op3", replaced);
// test form substitution with new forms
script = "INPUT=${input_form=}SELECTED=${select_form(Selection Form)=,s_op1|s_op2|s_op3}\n" +
"CHECKED=${checkbox:checkbox_form=c_op1|c_op2,c_op1|c_op2|c_op3}\n" +
"NEW_CHECKED=${checkbox( and ):new_check=nc_a|nc_c,nc_a|nc_b|nc_c}";
replaced = Input.getSimpleQuery(params, script);
assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1,c_op3\n" +
"NEW_CHECKED=nc_a and nc_c", replaced);
// test form substitution with obsoleted values
script = "INPUT=${input_form=}SELECTED=${select_form(Selection Form)=,s_op1|s_op2|s_op3}\n" +
"CHECKED=${checkbox:checkbox_form=c_op1|c_op2,c_op1|c_op2|c_op3_new}\n" +
"NEW_CHECKED=${checkbox( and ):new_check=nc_a|nc_c,nc_a|nc_b|nc_c}";
replaced = Input.getSimpleQuery(params, script);
assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1\n" +
"NEW_CHECKED=nc_a and nc_c", replaced);
}
}

View file

@ -61,6 +61,7 @@ public class RemoteAngularObjectTest implements AngularObjectRegistryListener {
intp = new RemoteInterpreter(
p,
"note",
MockInterpreterAngular.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -70,7 +71,8 @@ public class RemoteAngularObjectTest implements AngularObjectRegistryListener {
null
);
intpGroup.add(intp);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intp);
intp.setInterpreterGroup(intpGroup);
context = new InterpreterContext(

View file

@ -44,6 +44,8 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
@Before
public void setUp() throws Exception {
intpGroup = new InterpreterGroup();
intpGroup.put("note", new LinkedList<Interpreter>());
env = new HashMap<String, String>();
env.put("ZEPPELIN_CLASSPATH", new File("./target/test-classes").getAbsolutePath());
}
@ -57,6 +59,7 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
private RemoteInterpreter createMockInterpreter() {
RemoteInterpreter intp = new RemoteInterpreter(
new Properties(),
"note",
MockInterpreterOutputStream.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -65,7 +68,7 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
10 * 1000,
this);
intpGroup.add(intp);
intpGroup.get("note").add(intp);
intp.setInterpreterGroup(intpGroup);
return intp;
}

View file

@ -63,8 +63,13 @@ public class RemoteInterpreterTest {
}
private RemoteInterpreter createMockInterpreterA(Properties p) {
return createMockInterpreterA(p, "note");
}
private RemoteInterpreter createMockInterpreterA(Properties p, String noteId) {
return new RemoteInterpreter(
p,
noteId,
MockInterpreterA.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -75,8 +80,13 @@ public class RemoteInterpreterTest {
}
private RemoteInterpreter createMockInterpreterB(Properties p) {
return createMockInterpreterB(p, "note");
}
private RemoteInterpreter createMockInterpreterB(Properties p, String noteId) {
return new RemoteInterpreter(
p,
noteId,
MockInterpreterB.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -89,15 +99,17 @@ public class RemoteInterpreterTest {
@Test
public void testRemoteInterperterCall() throws TTransportException, IOException {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
RemoteInterpreter intpB = createMockInterpreterB(p);
intpGroup.add(intpB);
intpGroup.get("note").add(intpB);
intpB.setInterpreterGroup(intpGroup);
@ -108,10 +120,10 @@ public class RemoteInterpreterTest {
assertEquals(0, process.getNumIdleClient());
assertEquals(0, process.referenceCount());
intpA.open();
intpA.open(); // initializa all interpreters in the same group
assertTrue(process.isRunning());
assertEquals(1, process.getNumIdleClient());
assertEquals(1, process.referenceCount());
assertEquals(2, process.referenceCount());
intpA.interpret("1",
new InterpreterContext(
@ -144,7 +156,8 @@ public class RemoteInterpreterTest {
RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
@ -167,9 +180,11 @@ public class RemoteInterpreterTest {
@Test
public void testRemoteSchedulerSharing() throws TTransportException, IOException {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
RemoteInterpreter intpA = new RemoteInterpreter(
p,
"note",
MockInterpreterA.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -178,11 +193,13 @@ public class RemoteInterpreterTest {
10 * 1000,
null);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
RemoteInterpreter intpB = new RemoteInterpreter(
p,
"note",
MockInterpreterB.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -191,7 +208,7 @@ public class RemoteInterpreterTest {
10 * 1000,
null);
intpGroup.add(intpB);
intpGroup.get("note").add(intpB);
intpB.setInterpreterGroup(intpGroup);
intpA.open();
@ -236,15 +253,16 @@ public class RemoteInterpreterTest {
@Test
public void testRemoteSchedulerSharingSubmit() throws TTransportException, IOException, InterruptedException {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
final RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
final RemoteInterpreter intpB = createMockInterpreterB(p);
intpGroup.add(intpB);
intpGroup.get("note").add(intpB);
intpB.setInterpreterGroup(intpGroup);
intpA.open();
@ -322,13 +340,11 @@ public class RemoteInterpreterTest {
};
intpB.getScheduler().submit(jobB);
// wait until both job finished
while (jobA.getStatus() != Status.FINISHED ||
jobB.getStatus() != Status.FINISHED) {
Thread.sleep(100);
}
long end = System.currentTimeMillis();
assertTrue(end - start >= 1000);
@ -341,10 +357,11 @@ public class RemoteInterpreterTest {
@Test
public void testRunOrderPreserved() throws InterruptedException {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
final RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
@ -417,10 +434,11 @@ public class RemoteInterpreterTest {
public void testRunParallel() throws InterruptedException {
Properties p = new Properties();
p.put("parallel", "true");
intpGroup.put("note", new LinkedList<Interpreter>());
final RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
@ -507,6 +525,7 @@ public class RemoteInterpreterTest {
@Test
public void testInterpreterGroupResetAfterProcessFinished() {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
RemoteInterpreter intpA = createMockInterpreterA(p);
@ -525,10 +544,11 @@ public class RemoteInterpreterTest {
@Test
public void testInterpreterGroupResetDuringProcessRunning() throws InterruptedException {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
final RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
@ -577,7 +597,12 @@ public class RemoteInterpreterTest {
// restart interpreter
RemoteInterpreterProcess processA = intpA.getInterpreterProcess();
intpA.close();
intpA.setInterpreterGroup(new InterpreterGroup(intpA.getInterpreterGroup().getId()));
InterpreterGroup newInterpreterGroup =
new InterpreterGroup(intpA.getInterpreterGroup().getId());
newInterpreterGroup.put("note", new LinkedList<Interpreter>());
intpA.setInterpreterGroup(newInterpreterGroup);
intpA.open();
RemoteInterpreterProcess processB = intpA.getInterpreterProcess();
@ -588,15 +613,16 @@ public class RemoteInterpreterTest {
@Test
public void testRemoteInterpreterSharesTheSameSchedulerInstanceInTheSameGroup() {
Properties p = new Properties();
intpGroup.put("note", new LinkedList<Interpreter>());
RemoteInterpreter intpA = createMockInterpreterA(p);
intpGroup.add(intpA);
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
RemoteInterpreter intpB = createMockInterpreterB(p);
intpGroup.add(intpB);
intpGroup.get("note").add(intpB);
intpB.setInterpreterGroup(intpGroup);
intpA.open();
@ -604,4 +630,38 @@ public class RemoteInterpreterTest {
assertEquals(intpA.getScheduler(), intpB.getScheduler());
}
@Test
public void testMultiInterpreterSession() {
Properties p = new Properties();
intpGroup.put("sessionA", new LinkedList<Interpreter>());
intpGroup.put("sessionB", new LinkedList<Interpreter>());
RemoteInterpreter intpAsessionA = createMockInterpreterA(p, "sessionA");
intpGroup.get("sessionA").add(intpAsessionA);
intpAsessionA.setInterpreterGroup(intpGroup);
RemoteInterpreter intpBsessionA = createMockInterpreterB(p, "sessionA");
intpGroup.get("sessionA").add(intpBsessionA);
intpBsessionA.setInterpreterGroup(intpGroup);
intpAsessionA.open();
intpBsessionA.open();
assertEquals(intpAsessionA.getScheduler(), intpBsessionA.getScheduler());
RemoteInterpreter intpAsessionB = createMockInterpreterA(p, "sessionB");
intpGroup.get("sessionB").add(intpAsessionB);
intpAsessionB.setInterpreterGroup(intpGroup);
RemoteInterpreter intpBsessionB = createMockInterpreterB(p, "sessionB");
intpGroup.get("sessionB").add(intpBsessionB);
intpBsessionB.setInterpreterGroup(intpGroup);
intpAsessionB.open();
intpBsessionB.open();
assertEquals(intpAsessionB.getScheduler(), intpBsessionB.getScheduler());
assertNotEquals(intpAsessionA.getScheduler(), intpAsessionB.getScheduler());
}
}

View file

@ -91,13 +91,30 @@ public class MockInterpreterB extends Interpreter {
public MockInterpreterA getInterpreterA() {
InterpreterGroup interpreterGroup = getInterpreterGroup();
for (Interpreter intp : interpreterGroup) {
if (intp.getClassName().equals(MockInterpreterA.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
synchronized (interpreterGroup) {
for (List<Interpreter> interpreters : interpreterGroup.values()) {
boolean belongsToSameNoteGroup = false;
MockInterpreterA a = null;
for (Interpreter intp : interpreters) {
if (intp.getClassName().equals(MockInterpreterA.class.getName())) {
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
a = (MockInterpreterA) p;
}
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
if (this == p) {
belongsToSameNoteGroup = true;
}
}
if (belongsToSameNoteGroup) {
return a;
}
return (MockInterpreterA) p;
}
}
return null;
@ -105,13 +122,10 @@ public class MockInterpreterB extends Interpreter {
@Override
public Scheduler getScheduler() {
InterpreterGroup interpreterGroup = getInterpreterGroup();
for (Interpreter intp : interpreterGroup) {
if (intp.getClassName().equals(MockInterpreterA.class.getName())) {
return intp.getScheduler();
}
MockInterpreterA intpA = getInterpreterA();
if (intpA != null) {
return intpA.getScheduler();
}
return null;
}

View file

@ -58,6 +58,7 @@ public class DistributedResourcePoolTest {
intp1 = new RemoteInterpreter(
p,
"note",
MockInterpreterResourcePool.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -68,11 +69,13 @@ public class DistributedResourcePoolTest {
);
intpGroup1 = new InterpreterGroup("intpGroup1");
intpGroup1.add(intp1);
intpGroup1.put("note", new LinkedList<Interpreter>());
intpGroup1.get("note").add(intp1);
intp1.setInterpreterGroup(intpGroup1);
intp2 = new RemoteInterpreter(
p,
"note",
MockInterpreterResourcePool.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -83,7 +86,8 @@ public class DistributedResourcePoolTest {
);
intpGroup2 = new InterpreterGroup("intpGroup2");
intpGroup2.add(intp2);
intpGroup2.put("note", new LinkedList<Interpreter>());
intpGroup2.get("note").add(intp2);
intp2.setInterpreterGroup(intpGroup2);
context = new InterpreterContext(

View file

@ -31,6 +31,7 @@ import java.util.Properties;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterGroup;
@ -68,6 +69,7 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
final RemoteInterpreter intpA = new RemoteInterpreter(
p,
"note",
MockInterpreterA.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -76,12 +78,13 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
10 * 1000,
this);
intpGroup.add(intpA);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
Scheduler scheduler = schedulerSvc.createOrGetRemoteScheduler("test",
Scheduler scheduler = schedulerSvc.createOrGetRemoteScheduler("test", "note",
intpA.getInterpreterProcess(),
10);
@ -154,6 +157,7 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
final RemoteInterpreter intpA = new RemoteInterpreter(
p,
"note",
MockInterpreterA.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
@ -162,12 +166,13 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
10 * 1000,
this);
intpGroup.add(intpA);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
Scheduler scheduler = schedulerSvc.createOrGetRemoteScheduler("test",
Scheduler scheduler = schedulerSvc.createOrGetRemoteScheduler("test", "note",
intpA.getInterpreterProcess(),
10);

View file

@ -94,11 +94,10 @@ public class InterpreterRestApi {
NewInterpreterSettingRequest.class);
Properties p = new Properties();
p.putAll(request.getProperties());
// Option is deprecated from API, always use remote = true
InterpreterGroup interpreterGroup = interpreterFactory.add(request.getName(),
request.getGroup(),
request.getDependencies(),
new InterpreterOption(true),
request.getOption(),
p);
InterpreterSetting setting = interpreterFactory.get(interpreterGroup.getId());
logger.info("new setting created with {}", setting.id());
@ -126,9 +125,8 @@ public class InterpreterRestApi {
try {
UpdateInterpreterSettingRequest request = gson.fromJson(message,
UpdateInterpreterSettingRequest.class);
// Option is deprecated from API, always use remote = true
interpreterFactory.setPropertyAndRestart(settingId,
new InterpreterOption(true),
request.getOption(),
request.getProperties(),
request.getDependencies());
} catch (InterpreterException e) {

View file

@ -18,6 +18,8 @@
package org.apache.zeppelin.rest;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -46,6 +48,7 @@ import org.apache.zeppelin.rest.message.RunParagraphWithParametersRequest;
import org.apache.zeppelin.search.SearchService;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.socket.NotebookServer;
import org.apache.zeppelin.utils.SecurityUtils;
import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -75,6 +78,66 @@ public class NotebookRestApi {
this.notebookIndex = search;
}
/**
* list note owners
*/
@GET
@Path("{noteId}/permissions")
public Response getNotePermissions(@PathParam("noteId") String noteId) {
Note note = notebook.getNote(noteId);
HashMap<String, HashSet> permissionsMap = new HashMap<String, HashSet>();
permissionsMap.put("owners", note.getOwners());
permissionsMap.put("readers", note.getReaders());
permissionsMap.put("writers", note.getWriters());
return new JsonResponse<>(Status.OK, "", permissionsMap).build();
}
String ownerPermissionError(HashSet<String> current,
HashSet<String> allowed) throws IOException {
LOG.info("Cannot change permissions. Connection owners {}. Allowed owners {}",
current.toString(), allowed.toString());
return "Insufficient privileges to change permissions.\n\n" +
"Allowed owners: " + allowed.toString() + "\n\n" +
"User belongs to: " + current.toString();
}
/**
* Set note owners
*/
@PUT
@Path("{noteId}/permissions")
public Response putNotePermissions(@PathParam("noteId") String noteId, String req)
throws IOException {
HashMap<String, HashSet> permMap = gson.fromJson(req,
new TypeToken<HashMap<String, HashSet>>(){}.getType());
Note note = notebook.getNote(noteId);
String principal = SecurityUtils.getPrincipal();
HashSet<String> roles = SecurityUtils.getRoles();
LOG.info("Set permissions {} {} {} {} {}",
noteId,
principal,
permMap.get("owners"),
permMap.get("readers"),
permMap.get("writers")
);
HashSet<String> userAndRoles = new HashSet<String>();
userAndRoles.add(principal);
userAndRoles.addAll(roles);
if (!note.isOwner(userAndRoles)) {
return new JsonResponse<>(Status.FORBIDDEN, ownerPermissionError(userAndRoles,
note.getOwners())).build();
}
note.setOwners(permMap.get("owners"));
note.setReaders(permMap.get("readers"));
note.setWriters(permMap.get("writers"));
LOG.debug("After set permissions {} {} {}", note.getOwners(), note.getReaders(),
note.getWriters());
note.persist();
notebookServer.broadcastNote(note);
return new JsonResponse<>(Status.OK).build();
}
/**
* bind a setting to note
* @throws IOException
@ -102,7 +165,7 @@ public class NotebookRestApi {
setting.id(),
setting.getName(),
setting.getGroup(),
setting.getInterpreterGroup(),
setting.getInterpreterInfos(),
true)
);
}
@ -122,7 +185,7 @@ public class NotebookRestApi {
setting.id(),
setting.getName(),
setting.getGroup(),
setting.getInterpreterGroup(),
setting.getInterpreterInfos(),
false)
);
}

View file

@ -21,12 +21,15 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.utils.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
@ -36,6 +39,8 @@ import java.util.Map;
@Path("/security")
@Produces("application/json")
public class SecurityRestApi {
private static final Logger LOG = LoggerFactory.getLogger(SecurityRestApi.class);
/**
* Required by Swagger.
*/
@ -56,6 +61,7 @@ public class SecurityRestApi {
public Response ticket() {
ZeppelinConfiguration conf = ZeppelinConfiguration.create();
String principal = SecurityUtils.getPrincipal();
HashSet<String> roles = SecurityUtils.getRoles();
JsonResponse response;
// ticket set to anonymous for anonymous user. Simplify testing.
String ticket;
@ -66,9 +72,11 @@ public class SecurityRestApi {
Map<String, String> data = new HashMap<>();
data.put("principal", principal);
data.put("roles", roles.toString());
data.put("ticket", ticket);
response = new JsonResponse(Response.Status.OK, "", data);
LOG.warn(response.toString());
return response.build();
}
}

View file

@ -20,6 +20,7 @@ package org.apache.zeppelin.rest.message;
import java.util.List;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterSetting;
/**
* InterpreterSetting information for binding
@ -29,10 +30,12 @@ public class InterpreterSettingListForNoteBind {
String name;
String group;
private boolean selected;
private List<Interpreter> interpreters;
private List<InterpreterSetting.InterpreterInfo> interpreters;
public InterpreterSettingListForNoteBind(String id, String name,
String group, List<Interpreter> interpreters, boolean selected) {
String group,
List<InterpreterSetting.InterpreterInfo> interpreters,
boolean selected) {
super();
this.id = id;
this.name = name;
@ -65,11 +68,11 @@ public class InterpreterSettingListForNoteBind {
this.group = group;
}
public List<Interpreter> getInterpreterNames() {
public List<InterpreterSetting.InterpreterInfo> getInterpreterNames() {
return interpreters;
}
public void setInterpreterNames(List<Interpreter> interpreters) {
public void setInterpreterNames(List<InterpreterSetting.InterpreterInfo> interpreters) {
this.interpreters = interpreters;
}

View file

@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.apache.zeppelin.dep.Dependency;
import org.apache.zeppelin.interpreter.InterpreterOption;
/**
* NewInterpreterSetting rest api request message
@ -29,9 +30,10 @@ import org.apache.zeppelin.dep.Dependency;
public class NewInterpreterSettingRequest {
String name;
String group;
// option was deprecated
Map<String, String> properties;
List<Dependency> dependencies;
InterpreterOption option;
public NewInterpreterSettingRequest() {
@ -52,4 +54,8 @@ public class NewInterpreterSettingRequest {
public List<Dependency> getDependencies() {
return dependencies;
}
public InterpreterOption getOption() {
return option;
}
}

View file

@ -21,19 +21,21 @@ import java.util.List;
import java.util.Properties;
import org.apache.zeppelin.dep.Dependency;
import org.apache.zeppelin.interpreter.InterpreterOption;
/**
* UpdateInterpreterSetting rest api request message
*/
public class UpdateInterpreterSettingRequest {
// option was deprecated
Properties properties;
List<Dependency> dependencies;
InterpreterOption option;
public UpdateInterpreterSettingRequest(Properties properties,
List<Dependency> dependencies) {
List<Dependency> dependencies, InterpreterOption option) {
this.properties = properties;
this.dependencies = dependencies;
this.option = option;
}
public Properties getProperties() {
@ -43,4 +45,8 @@ public class UpdateInterpreterSettingRequest {
public List<Dependency> getDependencies() {
return dependencies;
}
public InterpreterOption getOption() {
return option;
}
}

View file

@ -23,17 +23,14 @@ import org.apache.zeppelin.interpreter.InterpreterOption;
/**
* Created by eranw on 8/30/15.
* Omit InterpreterOption from serialization
*/
public class JsonExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> arg0) {
//exclude only InterpreterOption
return InterpreterOption.class.equals(arg0);
return false;
}
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
}

View file

@ -23,10 +23,11 @@ import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response.ResponseBuilder;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterSerializer;
import org.apache.zeppelin.interpreter.InterpreterInfoSerializer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.zeppelin.interpreter.InterpreterSetting;
/**
* Json response builder.
@ -98,8 +99,9 @@ public class JsonResponse<T> {
@Override
public String toString() {
GsonBuilder gsonBuilder = new GsonBuilder()
.registerTypeAdapter(Interpreter.class, new InterpreterSerializer());
GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(
InterpreterSetting.InterpreterInfo.class,
new InterpreterInfoSerializer());
if (pretty) {
gsonBuilder.setPrettyPrinting();
}

View file

@ -96,6 +96,7 @@ public class Message {
PARAGRAPH_APPEND_OUTPUT, // [s-c] append output
PARAGRAPH_UPDATE_OUTPUT, // [s-c] update (replace) output
PING,
AUTH_INFO,
ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object
ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del
@ -116,6 +117,7 @@ public class Message {
public Map<String, Object> data = new HashMap<String, Object>();
public String ticket = "anonymous";
public String principal = "anonymous";
public String roles = "";
public Message(OP op) {
this.op = op;

View file

@ -18,6 +18,8 @@ package org.apache.zeppelin.socket;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.display.AngularObject;
@ -96,6 +98,7 @@ public class NotebookServer extends WebSocketServlet implements
LOG.debug("RECEIVE << " + messagereceived.op);
LOG.debug("RECEIVE PRINCIPAL << " + messagereceived.principal);
LOG.debug("RECEIVE TICKET << " + messagereceived.ticket);
LOG.debug("RECEIVE ROLES << " + messagereceived.roles);
String ticket = TicketContainer.instance.getTicket(messagereceived.principal);
if (ticket != null && !ticket.equals(messagereceived.ticket))
throw new Exception("Invalid ticket " + messagereceived.ticket + " != " + ticket);
@ -107,6 +110,16 @@ public class NotebookServer extends WebSocketServlet implements
throw new Exception("Anonymous access not allowed ");
}
HashSet<String> userAndRoles = new HashSet<String>();
userAndRoles.add(messagereceived.principal);
if (!messagereceived.roles.equals("")) {
HashSet<String> roles = gson.fromJson(messagereceived.roles,
new TypeToken<HashSet<String>>(){}.getType());
if (roles != null) {
userAndRoles.addAll(roles);
}
}
/** Lets be elegant here */
switch (messagereceived.op) {
case LIST_NOTES:
@ -116,57 +129,57 @@ public class NotebookServer extends WebSocketServlet implements
broadcastReloadedNoteList();
break;
case GET_HOME_NOTE:
sendHomeNote(conn, notebook);
sendHomeNote(conn, userAndRoles, notebook);
break;
case GET_NOTE:
sendNote(conn, notebook, messagereceived);
sendNote(conn, userAndRoles, notebook, messagereceived);
break;
case NEW_NOTE:
createNote(conn, notebook, messagereceived);
createNote(conn, userAndRoles, notebook, messagereceived);
break;
case DEL_NOTE:
removeNote(conn, notebook, messagereceived);
removeNote(conn, userAndRoles, notebook, messagereceived);
break;
case CLONE_NOTE:
cloneNote(conn, notebook, messagereceived);
cloneNote(conn, userAndRoles, notebook, messagereceived);
break;
case IMPORT_NOTE:
importNote(conn, notebook, messagereceived);
importNote(conn, userAndRoles, notebook, messagereceived);
break;
case COMMIT_PARAGRAPH:
updateParagraph(conn, notebook, messagereceived);
updateParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case RUN_PARAGRAPH:
runParagraph(conn, notebook, messagereceived);
runParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case CANCEL_PARAGRAPH:
cancelParagraph(conn, notebook, messagereceived);
cancelParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case MOVE_PARAGRAPH:
moveParagraph(conn, notebook, messagereceived);
moveParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case INSERT_PARAGRAPH:
insertParagraph(conn, notebook, messagereceived);
insertParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case PARAGRAPH_REMOVE:
removeParagraph(conn, notebook, messagereceived);
removeParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case PARAGRAPH_CLEAR_OUTPUT:
clearParagraphOutput(conn, notebook, messagereceived);
clearParagraphOutput(conn, userAndRoles, notebook, messagereceived);
break;
case NOTE_UPDATE:
updateNote(conn, notebook, messagereceived);
updateNote(conn, userAndRoles, notebook, messagereceived);
break;
case COMPLETION:
completion(conn, notebook, messagereceived);
completion(conn, userAndRoles, notebook, messagereceived);
break;
case PING:
break; //do nothing
case ANGULAR_OBJECT_UPDATED:
angularObjectUpdated(conn, notebook, messagereceived);
angularObjectUpdated(conn, userAndRoles, notebook, messagereceived);
break;
case LIST_CONFIGURATIONS:
sendAllConfigurations(conn, notebook);
sendAllConfigurations(conn, userAndRoles, notebook);
break;
case CHECKPOINT_NOTEBOOK:
checkpointNotebook(conn, notebook, messagereceived);
@ -358,8 +371,24 @@ public class NotebookServer extends WebSocketServlet implements
broadcastAll(new Message(OP.NOTES_INFO).put("notes", notesInfo));
}
private void sendNote(NotebookSocket conn, Notebook notebook,
void permissionError(NotebookSocket conn, String op, HashSet<String> current,
HashSet<String> allowed) throws IOException {
LOG.info("Cannot {}. Connection readers {}. Allowed readers {}",
op, current, allowed);
conn.send(serializeMessage(new Message(OP.AUTH_INFO).put("info",
"Insufficient privileges to " + op + " note.\n\n" +
"Allowed users or roles: " + allowed.toString() + "\n\n" +
"User belongs to: " + current.toString())));
}
private void sendNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
LOG.info("New operation from {} : {} : {} : {} : {}", conn.getRequest().getRemoteAddr(),
conn.getRequest().getRemotePort(),
fromMessage.principal, fromMessage.op, fromMessage.get("id")
);
String noteId = (String) fromMessage.get("id");
if (noteId == null) {
return;
@ -367,13 +396,19 @@ public class NotebookServer extends WebSocketServlet implements
Note note = notebook.getNote(noteId);
if (note != null) {
if (!note.isReader(userAndRoles)) {
permissionError(conn, "read", userAndRoles, note.getReaders());
broadcastNoteList();
return;
}
addConnectionToNote(note.id(), conn);
conn.send(serializeMessage(new Message(OP.NOTE).put("note", note)));
sendAllAngularObjects(note, conn);
}
}
private void sendHomeNote(NotebookSocket conn, Notebook notebook) throws IOException {
private void sendHomeNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook) throws IOException {
String noteId = notebook.getConf().getString(ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN);
Note note = null;
@ -382,6 +417,11 @@ public class NotebookServer extends WebSocketServlet implements
}
if (note != null) {
if (!note.isReader(userAndRoles)) {
permissionError(conn, "read", userAndRoles, note.getReaders());
broadcastNoteList();
return;
}
addConnectionToNote(note.id(), conn);
conn.send(serializeMessage(new Message(OP.NOTE).put("note", note)));
sendAllAngularObjects(note, conn);
@ -391,7 +431,8 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void updateNote(WebSocket conn, Notebook notebook, Message fromMessage)
private void updateNote(WebSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
String noteId = (String) fromMessage.get("id");
String name = (String) fromMessage.get("name");
@ -433,7 +474,8 @@ public class NotebookServer extends WebSocketServlet implements
return cronUpdated;
}
private void createNote(NotebookSocket conn, Notebook notebook, Message message)
private void createNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message message)
throws IOException {
Note note = notebook.createNote();
note.addParagraph(); // it's an empty note. so add one paragraph
@ -451,7 +493,8 @@ public class NotebookServer extends WebSocketServlet implements
broadcastNoteList();
}
private void removeNote(WebSocket conn, Notebook notebook, Message fromMessage)
private void removeNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws IOException {
String noteId = (String) fromMessage.get("id");
if (noteId == null) {
@ -459,13 +502,19 @@ public class NotebookServer extends WebSocketServlet implements
}
Note note = notebook.getNote(noteId);
if (!note.isOwner(userAndRoles)) {
permissionError(conn, "remove", userAndRoles, note.getOwners());
return;
}
notebook.removeNote(noteId);
removeNote(noteId);
broadcastNoteList();
}
private void updateParagraph(NotebookSocket conn, Notebook notebook,
Message fromMessage) throws IOException {
private void updateParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
return;
@ -476,6 +525,12 @@ public class NotebookServer extends WebSocketServlet implements
Map<String, Object> config = (Map<String, Object>) fromMessage
.get("config");
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
Paragraph p = note.getParagraph(paragraphId);
p.settings.setParams(params);
p.setConfig(config);
@ -485,7 +540,8 @@ public class NotebookServer extends WebSocketServlet implements
broadcast(note.id(), new Message(OP.PARAGRAPH).put("paragraph", p));
}
private void cloneNote(NotebookSocket conn, Notebook notebook, Message fromMessage)
private void cloneNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws IOException, CloneNotSupportedException {
String noteId = getOpenNoteId(conn);
String name = (String) fromMessage.get("name");
@ -495,7 +551,8 @@ public class NotebookServer extends WebSocketServlet implements
broadcastNoteList();
}
protected Note importNote(NotebookSocket conn, Notebook notebook, Message fromMessage)
protected Note importNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws IOException {
Note note = null;
if (fromMessage != null) {
@ -509,14 +566,20 @@ public class NotebookServer extends WebSocketServlet implements
return note;
}
private void removeParagraph(NotebookSocket conn, Notebook notebook,
Message fromMessage) throws IOException {
private void removeParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
return;
}
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
/** We dont want to remove the last paragraph */
if (!note.isLastParagraph(paragraphId)) {
note.removeParagraph(paragraphId);
@ -525,19 +588,25 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void clearParagraphOutput(NotebookSocket conn, Notebook notebook,
Message fromMessage) throws IOException {
private void clearParagraphOutput(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
return;
}
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
note.clearParagraphOutput(paragraphId);
broadcastNote(note);
}
private void completion(NotebookSocket conn, Notebook notebook,
private void completion(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
String paragraphId = (String) fromMessage.get("id");
String buffer = (String) fromMessage.get("buf");
@ -561,8 +630,8 @@ public class NotebookServer extends WebSocketServlet implements
* @param notebook the notebook.
* @param fromMessage the message.
*/
private void angularObjectUpdated(NotebookSocket conn, Notebook notebook,
Message fromMessage) {
private void angularObjectUpdated(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) {
String noteId = (String) fromMessage.get("noteId");
String paragraphId = (String) fromMessage.get("paragraphId");
String interpreterGroupId = (String) fromMessage.get("interpreterGroupId");
@ -644,7 +713,7 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void moveParagraph(NotebookSocket conn, Notebook notebook,
private void moveParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
@ -654,22 +723,34 @@ public class NotebookServer extends WebSocketServlet implements
final int newIndex = (int) Double.parseDouble(fromMessage.get("index")
.toString());
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
note.moveParagraph(paragraphId, newIndex);
note.persist();
broadcastNote(note);
}
private void insertParagraph(NotebookSocket conn, Notebook notebook,
Message fromMessage) throws IOException {
private void insertParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
final int index = (int) Double.parseDouble(fromMessage.get("index")
.toString());
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
note.insertParagraph(index);
note.persist();
broadcastNote(note);
}
private void cancelParagraph(NotebookSocket conn, Notebook notebook,
private void cancelParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
@ -677,11 +758,17 @@ public class NotebookServer extends WebSocketServlet implements
}
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
Paragraph p = note.getParagraph(paragraphId);
p.abort();
}
private void runParagraph(NotebookSocket conn, Notebook notebook,
private void runParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
@ -689,6 +776,12 @@ public class NotebookServer extends WebSocketServlet implements
}
final Note note = notebook.getNote(getOpenNoteId(conn));
if (!note.isWriter(userAndRoles)) {
permissionError(conn, "write", userAndRoles, note.getWriters());
return;
}
Paragraph p = note.getParagraph(paragraphId);
String text = (String) fromMessage.get("paragraph");
p.setText(text);
@ -729,8 +822,8 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void sendAllConfigurations(NotebookSocket conn, Notebook notebook)
throws IOException {
private void sendAllConfigurations(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook) throws IOException {
ZeppelinConfiguration conf = notebook.getConf();
Map<String, String> configurations = conf.dumpConfigurations(conf,

View file

@ -23,6 +23,8 @@ import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
/**
* Tools for securing Zeppelin
@ -52,6 +54,7 @@ public class SecurityUtils {
*/
public static String getPrincipal() {
Subject subject = org.apache.shiro.SecurityUtils.getSubject();
String principal;
if (subject.isAuthenticated()) {
principal = subject.getPrincipal().toString();
@ -61,4 +64,24 @@ public class SecurityUtils {
}
return principal;
}
/**
* Return the roles associated with the authenticated user if any otherwise returns empty set
* TODO(prasadwagle) Find correct way to get user roles (see SHIRO-492)
* @return shiro roles
*/
public static HashSet<String> getRoles() {
Subject subject = org.apache.shiro.SecurityUtils.getSubject();
HashSet<String> roles = new HashSet<>();
if (subject.isAuthenticated()) {
for (String role : Arrays.asList("role1", "role2", "role3")) {
if (subject.hasRole(role)) {
roles.add(role);
}
}
}
return roles;
}
}

View file

@ -30,6 +30,7 @@ import org.junit.rules.ErrorCollector;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.Select;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -298,4 +299,32 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
}
}
@Test
public void testWidth() throws Exception {
if (!endToEndTestEnabled()) {
return;
}
try {
createNewNote();
waitForParagraph(1, "READY");
collector.checkThat("Default Width is 12 ",
driver.findElement(By.xpath("//div[contains(@class,'col-md-12')]")).isDisplayed(),
CoreMatchers.equalTo(true));
for (Integer newWidth = 1; newWidth <= 11; newWidth++) {
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
String visibleText = newWidth.toString();
new Select(driver.findElement(By.xpath(getParagraphXPath(1)
+ "//ul/li/a/form/select[(@ng-change='changeColWidth()')]"))).selectByVisibleText(visibleText);
collector.checkThat("New Width is : " + newWidth,
driver.findElement(By.xpath("//div[contains(@class,'col-md-" + newWidth + "')]")).isDisplayed(),
CoreMatchers.equalTo(true));
}
} catch (Exception e) {
handleException("Exception in ParagraphActionsIT while testWidth ", e);
}
}
}

View file

@ -90,7 +90,8 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
// Call Create Setting REST API
String jsonRequest = "{\"name\":\"md2\",\"group\":\"md\",\"properties\":{\"propname\":\"propvalue\"}," +
"\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," +
"\"dependencies\":[]}";
"\"dependencies\":[]," +
"\"option\": { \"remote\": true, \"perNoteSession\": false }}";
PostMethod post = httpPost("/interpreter/setting/", jsonRequest);
LOG.info("testSettingCRUD create response\n" + post.getResponseBodyAsString());
assertThat("test create method:", post, isCreated());
@ -105,7 +106,8 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
// Call Update Setting REST API
jsonRequest = "{\"name\":\"md2\",\"group\":\"md\",\"properties\":{\"propname\":\"Otherpropvalue\"}," +
"\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," +
"\"dependencies\":[]}";
"\"dependencies\":[]," +
"\"option\": { \"remote\": true, \"perNoteSession\": false }}";
PutMethod put = httpPut("/interpreter/setting/" + newSettingId, jsonRequest);
LOG.info("testSettingCRUD update response\n" + put.getResponseBodyAsString());
assertThat("test update method:", put, isAllowed());

View file

@ -24,6 +24,7 @@ import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.rest.AbstractTestRestApi;
import org.apache.zeppelin.server.ZeppelinServer;
import org.apache.zeppelin.socket.Message.OP;
@ -86,14 +87,17 @@ public class NotebookServerTest extends AbstractTestRestApi {
InterpreterGroup interpreterGroup = null;
List<InterpreterSetting> settings = note1.getNoteReplLoader().getInterpreterSettings();
for (InterpreterSetting setting : settings) {
if (setting.getInterpreterGroup() == null) {
continue;
if (setting.getName().equals("md")) {
interpreterGroup = setting.getInterpreterGroup();
break;
}
interpreterGroup = setting.getInterpreterGroup();
break;
}
// start interpreter process
Paragraph p1 = note1.addParagraph();
p1.setText("%md start remote interpreter process");
note1.run(p1.getId());
// add angularObject
interpreterGroup.getAngularObjectRegistry().add("object1", "value1", note1.getId(), null);
@ -140,7 +144,7 @@ public class NotebookServerTest extends AbstractTestRestApi {
Message messageReceived = notebookServer.deserializeMessage(msg);
Note note = null;
try {
note = notebookServer.importNote(null, notebook, messageReceived);
note = notebookServer.importNote(null, null, notebook, messageReceived);
} catch (NullPointerException e) {
//broadcastNoteList(); failed nothing to worry.
LOG.error("Exception in NotebookServerTest while testImportNotebook, failed nothing to " +

View file

@ -163,9 +163,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>

View file

@ -35,6 +35,11 @@ limitations under the License.
</select>
</div>
<b>Option</b>
<div class="checkbox">
<label><input type="checkbox" style="top:-5px" ng-model="newInterpreterSetting.option.perNoteSession">Separate Interpreter for each note</input></label>
</div>
<b>Properties</b>
<table class="table table-striped properties">
<tr>

View file

@ -72,7 +72,17 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
$scope.addNewInterpreterDependency(settingId);
}
// add missing field of option
if (!setting.option) {
setting.option = {};
}
if (setting.option.remote === undefined) {
// remote always true for now
setting.option.remote = true;
}
var request = {
option: angular.copy(setting.option),
properties: angular.copy(setting.properties),
dependencies: angular.copy(setting.dependencies)
};
@ -214,7 +224,11 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
name: undefined,
group: undefined,
properties: {},
dependencies: []
dependencies: [],
option: {
remote: true,
perNoteSession: false
}
};
emptyNewProperty($scope.newInterpreterSetting);
};

View file

@ -109,6 +109,19 @@ limitations under the License.
</div>
</div>
<div class="row interpreter">
<div class="col-md-12">
<h5>Option</h5>
<div class="checkbox">
<label>
<input type="checkbox"
style="top:-5px"
ng-disabled="!valueform.$visible"
ng-model="setting.option.perNoteSession">
Separate Interpreter for each note</input>
</label>
</div>
</div>
<div ng-show="_.isEmpty(setting.properties) && _.isEmpty(setting.dependencies) || valueform.$hidden" class="col-md-12 gray40-message">
<em>Currently there are no properties and dependencies set for this interpreter</em>
</div>

View file

@ -155,6 +155,11 @@ limitations under the License.
tooltip-placement="bottom" tooltip="Interpreter binding">
<i class="fa fa-cog" ng-style="{color: showSetting ? '#3071A9' : 'black' }"></i>
</span>
<span style="position:relative; top:2px; margin-right:4px; cursor:pointer;"
ng-click="togglePermissions()"
tooltip-placement="bottom" tooltip="Note permissions">
<i class="fa fa-lock" ng-style="{color: showSetting ? '#3071A9' : 'black' }"></i>
</span>
<span class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle"

View file

@ -632,6 +632,69 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
}
};
var getPermissions = function(callback) {
$http.get(baseUrlSrv.getRestApiBase()+ '/notebook/' +$scope.note.id + '/permissions').
success(function(data, status, headers, config) {
$scope.permissions = data.body;
$scope.permissionsOrig = angular.copy($scope.permissions); // to check dirty
if (callback) {
callback();
}
}).
error(function(data, status, headers, config) {
if (status !== 0) {
console.log('Error %o %o', status, data.message);
}
});
};
$scope.openPermissions = function() {
$scope.showPermissions = true;
getPermissions();
};
$scope.closePermissions = function() {
if (isPermissionsDirty()) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Changes will be discarded.',
callback: function(result) {
if (result) {
$scope.$apply(function() {
$scope.showPermissions = false;
});
}
}
});
} else {
$scope.showPermissions = false;
}
};
$scope.savePermissions = function() {
$http.put(baseUrlSrv.getRestApiBase() + '/notebook/' +$scope.note.id + '/permissions',
$scope.permissions, {withCredentials: true}).
success(function(data, status, headers, config) {
console.log('Note permissions %o saved', $scope.permissions);
$scope.showPermissions = false;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
alert(data.message);
});
};
$scope.togglePermissions = function() {
if ($scope.showPermissions) {
$scope.closePermissions();
} else {
$scope.openPermissions();
}
};
var isSettingDirty = function() {
if (angular.equals($scope.interpreterBindings, $scope.interpreterBindingsOrig)) {
return false;
@ -639,4 +702,13 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
return true;
}
};
var isPermissionsDirty = function() {
if (angular.equals($scope.permissions, $scope.permissionsOrig)) {
return false;
} else {
return true;
}
};
});

View file

@ -132,6 +132,28 @@
overflow: hidden !important;
}
/*
Note Permissions Panel
*/
.permissions {
background: white;
padding: 10px 15px 15px 15px;
margin-left: -10px;
margin-right: -10px;
font-family: 'Roboto', sans-serif;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
border-bottom: 1px solid #E5E5E5;
}
.permissions .permissionsForm {
list-style-type: none;
background: #EFEFEF;
padding: 10px 10px 10px 10px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
border: 1px solid #E5E5E5;
}
/*
Note Setting Panel
*/

View file

@ -14,6 +14,30 @@ limitations under the License.
<!-- Here the controller <NotebookCtrl> is not needed because explicitly set in the app.js (route) -->
<div ng-include src="'app/notebook/notebook-actionBar.html'"></div>
<div style="padding-top: 36px;">
<!-- permissions -->
<div ng-if="showPermissions" class="permissions">
<div>
<h4>Note Permissions (Only note owners can change)</h4>
</div>
<hr />
<div>
<p>
Enter comma separated users and groups in the fields. <br />
Empty field (*) implies anyone can do the operation.
</p>
<div class="permissionsForm"
data-ng-model="permissions">
<p>Owners : <input ng-list ng-model="permissions.owners" placeholder="*"> Owners can change permissions, read and write the note. </p>
<p>Readers : <input ng-list ng-model="permissions.readers" placeholder="*"> Readers can only read the note.</p>
<p>Writers : <input ng-list ng-model="permissions.writers" placeholder="*"> Writers can read and write the note.</p>
</div>
</div>
<br />
<div>
<button class="btn btn-primary" ng-click="savePermissions()">Save</button>
<button class="btn btn-default" ng-click="closePermissions()">Cancel</button>
</div>
</div>
<!-- settings -->
<div ng-if="showSetting" class="setting">
<div>

View file

@ -39,6 +39,10 @@ limitations under the License.
type="button">
</span>
<ul class="dropdown-menu" role="menu" style="width:200px;z-index:1002">
<li ng-click="$event.stopPropagation()" style="text-align:center;margin-top:4px;">
{{paragraph.id}}
</li>
<li role="separator" class="divider"></li>
<li>
<a ng-click="$event.stopPropagation()" class="dropdown"><span class="fa fa-arrows-h"></span> Width
<form style="display:inline; margin-left:5px;">

View file

@ -28,13 +28,24 @@ limitations under the License.
name="{{formulaire.name}}" />
<select class="form-control input-sm"
ng-if="paragraph.settings.forms[formulaire.name].options"
ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
ng-change="runParagraph(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}"
ng-options="option.value as (option.displayName||option.value) for option in paragraph.settings.forms[formulaire.name].options">
</select>
<div ng-if="paragraph.settings.forms[formulaire.name].type == 'checkbox'">
<label ng-repeat="option in paragraph.settings.forms[formulaire.name].options"
class="checkbox-item input-sm">
<input type="checkbox"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
ng-checked="paragraph.settings.params[formulaire.name].indexOf(option.value) > -1"
ng-click="toggleCheckbox(formulaire, option, false)"/>{{option.displayName||option.value}}
</label>
</div>
</div>
</div>
</form>

View file

@ -629,13 +629,18 @@ angular.module('zeppelinWebApp')
value = params[formulaire.name];
}
if (value === '') {
value = formulaire.options[0].value;
}
$scope.paragraph.settings.params[formulaire.name] = value;
};
$scope.toggleCheckbox = function(formulaire, option) {
var idx = $scope.paragraph.settings.params[formulaire.name].indexOf(option.value);
if (idx > -1) {
$scope.paragraph.settings.params[formulaire.name].splice(idx, 1);
} else {
$scope.paragraph.settings.params[formulaire.name].push(option.value);
}
};
$scope.aceChanged = function() {
$scope.dirtyText = $scope.editor.getSession().getValue();
$scope.startSaveTimer();
@ -968,6 +973,12 @@ angular.module('zeppelinWebApp')
} else {
$scope.showLineNumbers();
}
} else if (keyEvent.ctrlKey && keyEvent.shiftKey && keyCode === 189) { // Ctrl + Shift + -
$scope.paragraph.config.colWidth = Math.max(1, $scope.paragraph.config.colWidth - 1);
$scope.changeColWidth();
} else if (keyEvent.ctrlKey && keyEvent.shiftKey && keyCode === 187) { // Ctrl + Shift + =
$scope.paragraph.config.colWidth = Math.min(12, $scope.paragraph.config.colWidth + 1);
$scope.changeColWidth();
} else if (keyEvent.ctrlKey && keyEvent.altKey && ((keyCode >= 48 && keyCode <=57) || keyCode === 189 || keyCode === 187)) { // Ctrl + Alt + [1~9,0,-,=]
var colWidth = 12;
if (keyCode === 48) {

View file

@ -234,6 +234,15 @@
padding-left: 0;
}
.paragraphForm.form-horizontal .form-group .checkbox-item {
padding-left: 0;
padding-right: 10px;
}
.paragraphForm.form-horizontal .form-group .checkbox-item input {
margin-right: 2px;
}
/*
Ace Text Editor CSS
*/

View file

@ -32,3 +32,4 @@ body {
box-shadow: 0px 2px 7px rgba(0, 0, 0, 0.3);
border-color: white;
}

View file

@ -180,11 +180,22 @@ limitations under the License.
<div class="row">
<div class="col-md-4">
<div class="keys">
<kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">{{ isMac ? 'Option' : 'Alt'}}</kbd> + <kbd class="kbd-dark">1</kbd>~<kbd class="kbd-dark">0</kbd>,<kbd class="kbd-dark">-</kbd>,<kbd class="kbd-dark">+</kbd>
<kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Shift</kbd> + <kbd class="kbd-dark">-</kbd>
</div>
</div>
<div class="col-md-8">
Set paragraph width from 1 to 12
Reduce paragraph width
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="keys">
<kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Shift</kbd> + <kbd class="kbd-dark">+</kbd>
</div>
</div>
<div class="col-md-8">
Increase paragraph width
</div>
</div>

View file

@ -30,7 +30,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents', function($rootScope,
websocketCalls.sendNewEvent = function(data) {
data.principal = $rootScope.ticket.principal;
data.ticket = $rootScope.ticket.ticket;
console.log('Send >> %o, %o, %o, %o', data.op, data.principal, data.ticket, data);
data.roles = $rootScope.ticket.roles;
console.log('Send >> %o, %o, %o, %o, %o', data.op, data.principal, data.ticket, data.roles, data);
websocketCalls.ws.send(JSON.stringify(data));
};
@ -52,12 +53,14 @@ angular.module('zeppelinWebApp').factory('websocketEvents', function($rootScope,
$location.path('notebook/' + data.note.id);
} else if (op === 'NOTES_INFO') {
$rootScope.$broadcast('setNoteMenu', data.notes);
} else if (op === 'AUTH_INFO') {
alert(data.info.toString());
} else if (op === 'PARAGRAPH') {
$rootScope.$broadcast('updateParagraph', data);
} else if (op === 'PARAGRAPH_APPEND_OUTPUT') {
$rootScope.$broadcast('appendParagraphOutput', data);
} else if (op === 'PARAGRAPH_UPDATE_OUTPUT') {
$rootScope.$broadcast('updateParagraphOutput', data);
$rootScope.$broadcast('updateParagraphOutput', data);
} else if (op === 'PROGRESS') {
$rootScope.$broadcast('updateProgress', data);
} else if (op === 'COMPLETION_LIST') {

View file

@ -449,7 +449,7 @@ public class ZeppelinConfiguration extends XMLConfiguration {
+ "org.apache.zeppelin.livy.LivyPySparkInterpreter,"
+ "org.apache.zeppelin.livy.LivySparkRInterpreter,"
+ "org.apache.zeppelin.hive.HiveInterpreter,"
+ "org.apache.zeppelin.tachyon.TachyonInterpreter,"
+ "org.apache.zeppelin.alluxio.AlluxioInterpreter,"
+ "org.apache.zeppelin.phoenix.PhoenixInterpreter,"
+ "org.apache.zeppelin.postgresql.PostgreSqlInterpreter,"
+ "org.apache.zeppelin.tajo.TajoInterpreter,"

View file

@ -51,7 +51,6 @@ import java.util.*;
/**
* Manage interpreters.
*
*/
public class InterpreterFactory {
Logger logger = LoggerFactory.getLogger(InterpreterFactory.class);
@ -103,7 +102,8 @@ public class InterpreterFactory {
GsonBuilder builder = new GsonBuilder();
builder.setPrettyPrinting();
builder.registerTypeAdapter(Interpreter.class, new InterpreterSerializer());
builder.registerTypeAdapter(
InterpreterSetting.InterpreterInfo.class, new InterpreterInfoSerializer());
gson = builder.create();
init();
@ -197,16 +197,14 @@ public class InterpreterFactory {
InterpreterSetting setting = interpreterSettings.get(settingId);
logger.info("Interpreter setting group {} : id={}, name={}",
setting.getGroup(), settingId, setting.getName());
for (Interpreter interpreter : setting.getInterpreterGroup()) {
logger.info(" className = {}", interpreter.getClassName());
}
}
}
private void loadFromFile() throws IOException {
GsonBuilder builder = new GsonBuilder();
builder.setPrettyPrinting();
builder.registerTypeAdapter(Interpreter.class, new InterpreterSerializer());
builder.registerTypeAdapter(
InterpreterSetting.InterpreterInfo.class, new InterpreterInfoSerializer());
Gson gson = builder.create();
File settingFile = new File(conf.getInterpreterSettingPath());
@ -241,14 +239,12 @@ public class InterpreterFactory {
setting.id(),
setting.getName(),
setting.getGroup(),
setting.getInterpreterInfos(),
setting.getProperties(),
setting.getDependencies(),
setting.getOption());
InterpreterGroup interpreterGroup = createInterpreterGroup(
setting.id(),
setting.getGroup(),
setting.getOption(),
setting.getProperties());
InterpreterGroup interpreterGroup = createInterpreterGroup(setting.id(), setting.getOption());
intpSetting.setInterpreterGroup(interpreterGroup);
interpreterSettings.put(k, intpSetting);
@ -380,9 +376,27 @@ public class InterpreterFactory {
throws InterpreterException, IOException, RepositoryException {
synchronized (interpreterSettings) {
List<InterpreterSetting.InterpreterInfo> interpreterInfos =
new LinkedList<InterpreterSetting.InterpreterInfo>();
for (RegisteredInterpreter registeredInterpreter :
Interpreter.registeredInterpreters.values()) {
if (registeredInterpreter.getGroup().equals(groupName)) {
for (String className : interpreterClassList) {
if (registeredInterpreter.getClassName().equals(className)) {
interpreterInfos.add(
new InterpreterSetting.InterpreterInfo(
className, registeredInterpreter.getName()));
}
}
}
}
InterpreterSetting intpSetting = new InterpreterSetting(
name,
groupName,
interpreterInfos,
properties,
dependencies,
option);
@ -390,8 +404,7 @@ public class InterpreterFactory {
loadInterpreterDependencies(intpSetting);
}
InterpreterGroup interpreterGroup = createInterpreterGroup(
intpSetting.id(), groupName, option, properties);
InterpreterGroup interpreterGroup = createInterpreterGroup(intpSetting.id(), option);
intpSetting.setInterpreterGroup(interpreterGroup);
@ -401,18 +414,12 @@ public class InterpreterFactory {
}
}
private InterpreterGroup createInterpreterGroup(String id,
String groupName,
InterpreterOption option,
Properties properties)
private InterpreterGroup createInterpreterGroup(String id, InterpreterOption option)
throws InterpreterException, NullArgumentException {
//When called from REST API without option we receive NPE
if (option == null)
throw new NullArgumentException("option");
//When called from REST API without option we receive NPE
if (properties == null)
throw new NullArgumentException("properties");
AngularObjectRegistry angularObjectRegistry;
@ -430,19 +437,68 @@ public class InterpreterFactory {
}
interpreterGroup.setAngularObjectRegistry(angularObjectRegistry);
return interpreterGroup;
}
public void removeInterpretersForNote(InterpreterSetting interpreterSetting,
String noteId) {
if (!interpreterSetting.getOption().isPerNoteSession()) {
return;
}
InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup();
interpreterGroup.close(noteId);
interpreterGroup.destroy(noteId);
synchronized (interpreterGroup) {
interpreterGroup.remove(noteId);
interpreterGroup.notifyAll(); // notify createInterpreterForNote()
}
logger.info("Interpreter instance {} for note {} is removed",
interpreterSetting.getName(),
noteId);
}
public void createInterpretersForNote(
InterpreterSetting interpreterSetting,
String noteId) {
InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup();
String groupName = interpreterSetting.getGroup();
InterpreterOption option = interpreterSetting.getOption();
Properties properties = interpreterSetting.getProperties();
// if interpreters are already there, wait until they're being removed
synchronized (interpreterGroup) {
long interpreterRemovalWaitStart = System.nanoTime();
// interpreter process supposed to be terminated by RemoteInterpreterProcess.dereference()
// in ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT msec. However, if termination of the process and
// removal from interpreter group take too long, throw an error.
long minTimeout = 10 * 1000 * 1000000; // 10 sec
long interpreterRemovalWaitTimeout =
Math.max(minTimeout, conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT) * 2);
while (interpreterGroup.containsKey(noteId)) {
if (System.nanoTime() - interpreterRemovalWaitStart > interpreterRemovalWaitTimeout) {
throw new InterpreterException("Can not create interpreter");
}
try {
interpreterGroup.wait(1000);
} catch (InterruptedException e) {
logger.debug(e.getMessage(), e);
}
}
}
logger.info("Create interpreter instance {} for note {}", interpreterSetting.getName(), noteId);
for (String className : interpreterClassList) {
Set<String> keys = Interpreter.registeredInterpreters.keySet();
for (String intName : keys) {
RegisteredInterpreter info = Interpreter.registeredInterpreters
.get(intName);
RegisteredInterpreter info = Interpreter.registeredInterpreters.get(intName);
if (info.getClassName().equals(className)
&& info.getGroup().equals(groupName)) {
Interpreter intp;
if (option.isRemote()) {
intp = createRemoteRepl(info.getPath(),
noteId,
info.getClassName(),
properties,
interpreterGroup.id);
@ -451,15 +507,25 @@ public class InterpreterFactory {
info.getClassName(),
properties);
}
interpreterGroup.add(intp);
synchronized (interpreterGroup) {
List<Interpreter> interpreters = interpreterGroup.get(noteId);
if (interpreters == null) {
interpreters = new LinkedList<Interpreter>();
interpreterGroup.put(noteId, interpreters);
}
interpreters.add(intp);
}
logger.info("Interpreter " + intp.getClassName() + " " + intp.hashCode() + " created");
intp.setInterpreterGroup(interpreterGroup);
break;
}
}
}
return interpreterGroup;
}
public void remove(String id) throws IOException {
synchronized (interpreterSettings) {
if (interpreterSettings.containsKey(id)) {
@ -486,7 +552,7 @@ public class InterpreterFactory {
}
/**
* Get loaded interpreters
* Get interpreter settings
* @return
*/
public List<InterpreterSetting> get() {
@ -508,8 +574,8 @@ public class InterpreterFactory {
continue;
}
}
for (InterpreterSetting.InterpreterInfo intp : setting.getInterpreterInfos()) {
for (Interpreter intp : setting.getInterpreterGroup()) {
if (className.equals(intp.getClassName())) {
boolean alreadyAdded = false;
for (InterpreterSetting st : orderedSettings) {
@ -536,15 +602,33 @@ public class InterpreterFactory {
public void putNoteInterpreterSettingBinding(String noteId,
List<String> settingList) throws IOException {
List<String> unBindedSettings = new LinkedList<String>();
synchronized (interpreterSettings) {
List<String> oldSettings = interpreterBindings.get(noteId);
if (oldSettings != null) {
for (String oldSettingId : oldSettings) {
if (!settingList.contains(oldSettingId)) {
unBindedSettings.add(oldSettingId);
}
}
}
interpreterBindings.put(noteId, settingList);
saveToFile();
for (String settingId : unBindedSettings) {
InterpreterSetting setting = get(settingId);
removeInterpretersForNote(setting, noteId);
}
}
}
public void removeNoteInterpreterSettingBinding(String noteId) {
synchronized (interpreterSettings) {
interpreterBindings.remove(noteId);
List<String> settingIds = interpreterBindings.remove(noteId);
for (String settingId : settingIds) {
this.removeInterpretersForNote(get(settingId), noteId);
}
}
}
@ -582,9 +666,7 @@ public class InterpreterFactory {
intpsetting.setOption(option);
intpsetting.setDependencies(dependencies);
InterpreterGroup interpreterGroup = createInterpreterGroup(
intpsetting.id(),
intpsetting.getGroup(), option, properties);
InterpreterGroup interpreterGroup = createInterpreterGroup(intpsetting.id(), option);
intpsetting.setInterpreterGroup(interpreterGroup);
loadInterpreterDependencies(intpsetting);
@ -608,7 +690,7 @@ public class InterpreterFactory {
InterpreterGroup interpreterGroup = createInterpreterGroup(
intpsetting.id(),
intpsetting.getGroup(), intpsetting.getOption(), intpsetting.getProperties());
intpsetting.getOption());
intpsetting.setInterpreterGroup(interpreterGroup);
} else {
throw new InterpreterException("Interpreter setting id " + id
@ -619,16 +701,18 @@ public class InterpreterFactory {
private void stopJobAllInterpreter(InterpreterSetting intpsetting) {
if (intpsetting != null) {
for (Interpreter intp : intpsetting.getInterpreterGroup()) {
for (Job job : intp.getScheduler().getJobsRunning()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
}
for (Job job : intp.getScheduler().getJobsWaiting()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
for (List<Interpreter> interpreters : intpsetting.getInterpreterGroup().values()) {
for (Interpreter intp : interpreters) {
for (Job job : intp.getScheduler().getJobsRunning()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
}
for (Job job : intp.getScheduler().getJobsWaiting()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
}
}
}
}
@ -720,13 +804,13 @@ public class InterpreterFactory {
}
private Interpreter createRemoteRepl(String interpreterPath, String className,
private Interpreter createRemoteRepl(String interpreterPath, String noteId, String className,
Properties property, String interpreterId) {
int connectTimeout = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT);
String localRepoPath = conf.getInterpreterLocalRepoPath() + "/" + interpreterId;
int maxPoolSize = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_MAX_POOL_SIZE);
LazyOpenInterpreter intp = new LazyOpenInterpreter(new RemoteInterpreter(
property, className, conf.getInterpreterRemoteRunnerPath(),
property, noteId, className, conf.getInterpreterRemoteRunnerPath(),
interpreterPath, localRepoPath, connectTimeout,
maxPoolSize, remoteInterpreterProcessListener));
return intp;

View file

@ -29,28 +29,30 @@ import com.google.gson.JsonSerializer;
/**
* Interpreter class serializer for gson
* InterpreterInfo class serializer for gson
*
*/
public class InterpreterSerializer implements JsonSerializer<Interpreter>,
JsonDeserializer<Interpreter> {
public class InterpreterInfoSerializer
implements JsonSerializer<InterpreterSetting.InterpreterInfo>,
JsonDeserializer<InterpreterSetting.InterpreterInfo> {
@Override
public JsonElement serialize(Interpreter interpreter, Type type,
public JsonElement serialize(InterpreterSetting.InterpreterInfo interpreterInfo, Type type,
JsonSerializationContext context) {
JsonObject json = new JsonObject();
json.addProperty("class", interpreter.getClassName());
json.addProperty(
"name",
Interpreter.findRegisteredInterpreterByClassName(
interpreter.getClassName()).getName());
json.addProperty("class", interpreterInfo.getClassName());
json.addProperty("name", interpreterInfo.getName());
return json;
}
@Override
public Interpreter deserialize(JsonElement json, Type typeOfT,
public InterpreterSetting.InterpreterInfo deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
return null;
JsonObject jsonObject = json.getAsJsonObject();
String className = jsonObject.get("class").getAsString();
String name = jsonObject.get("name").getAsString();
return new InterpreterSetting.InterpreterInfo(className, name);
}
}

View file

@ -22,6 +22,7 @@ package org.apache.zeppelin.interpreter;
*/
public class InterpreterOption {
boolean remote;
boolean perNoteSession;
public InterpreterOption() {
remote = false;
@ -38,4 +39,12 @@ public class InterpreterOption {
public void setRemote(boolean remote) {
this.remote = remote;
}
public boolean isPerNoteSession() {
return perNoteSession;
}
public void setPerNoteSession(boolean perNoteSession) {
this.perNoteSession = perNoteSession;
}
}

View file

@ -34,27 +34,59 @@ public class InterpreterSetting {
private String group;
private String description;
private Properties properties;
private InterpreterGroup interpreterGroup;
// use 'interpreterGroup' as a field name to keep backward compatibility of
// conf/interpreter.json file format
private List<InterpreterInfo> interpreterGroup;
private transient InterpreterGroup interpreterGroupRef;
private List<Dependency> dependencies;
private InterpreterOption option;
public InterpreterSetting(String id,
String name,
String group,
List<InterpreterInfo> interpreterInfos,
Properties properties,
List<Dependency> dependencies,
InterpreterOption option) {
this.id = id;
this.name = name;
this.group = group;
this.interpreterGroup = interpreterInfos;
this.properties = properties;
this.dependencies = dependencies;
this.option = option;
}
public InterpreterSetting(String name,
String group,
List<InterpreterInfo> interpreterInfos,
Properties properties,
List<Dependency> dependencies,
InterpreterOption option) {
this(generateId(), name, group, dependencies, option);
this(generateId(), name, group, interpreterInfos, properties, dependencies, option);
}
/**
* Information of interpreters in this interpreter setting.
* this will be serialized for conf/interpreter.json and REST api response.
*/
public static class InterpreterInfo {
private final String name;
private final String className;
public InterpreterInfo(String className, String name) {
this.className = className;
this.name = name;
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
}
public String id() {
@ -86,12 +118,11 @@ public class InterpreterSetting {
}
public InterpreterGroup getInterpreterGroup() {
return interpreterGroup;
return interpreterGroupRef;
}
public void setInterpreterGroup(InterpreterGroup interpreterGroup) {
this.interpreterGroup = interpreterGroup;
this.properties = interpreterGroup.getProperty();
this.interpreterGroupRef = interpreterGroup;
}
public Properties getProperties() {
@ -120,4 +151,8 @@ public class InterpreterSetting {
public void setOption(InterpreterOption option) {
this.option = option;
}
public List<InterpreterInfo> getInterpreterInfos() {
return interpreterGroup;
}
}

View file

@ -59,6 +59,9 @@ public class Note implements Serializable, JobListener {
private String name = "";
private String id;
private HashSet<String> owners = new HashSet<String>();
private HashSet<String> readers = new HashSet<String>();
private HashSet<String> writers = new HashSet<String>();
@SuppressWarnings("rawtypes")
Map<String, List<AngularObject>> angularObjects = new HashMap<>();
@ -115,6 +118,51 @@ public class Note implements Serializable, JobListener {
this.name = name;
}
public HashSet<String> getOwners() {
return (new HashSet<String>(owners));
}
public void setOwners(HashSet<String> owners) {
this.owners = new HashSet<String>(owners);
}
public HashSet<String> getReaders() {
return (new HashSet<String>(readers));
}
public void setReaders(HashSet<String> readers) {
this.readers = new HashSet<String>(readers);
}
public HashSet<String> getWriters() {
return (new HashSet<String>(writers));
}
public void setWriters(HashSet<String> writers) {
this.writers = new HashSet<String>(writers);
}
public boolean isOwner(HashSet<String> entities) {
return isMember(entities, this.owners);
}
public boolean isWriter(HashSet<String> entities) {
return isMember(entities, this.writers) || isMember(entities, this.owners);
}
public boolean isReader(HashSet<String> entities) {
return isMember(entities, this.readers) ||
isMember(entities, this.owners) ||
isMember(entities, this.writers);
}
// return true if b is empty or if (a intersection b) is non-empty
private boolean isMember(HashSet<String> a, HashSet<String> b) {
Set<String> intersection = new HashSet<String>(b);
intersection.retainAll(a);
return (b.isEmpty() || (intersection.size() > 0));
}
public NoteInterpreterLoader getNoteReplLoader() {
return replLoader;
}

View file

@ -29,10 +29,11 @@ import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterSetting;
/**
* Repl loader per note.
* Interpreter loader per note.
*/
public class NoteInterpreterLoader {
private transient InterpreterFactory factory;
private static String SHARED_SESSION = "shared_session";
String noteId;
public NoteInterpreterLoader(InterpreterFactory factory) {
@ -73,6 +74,37 @@ public class NoteInterpreterLoader {
return settings;
}
private String getInterpreterGroupKey(InterpreterSetting setting) {
if (!setting.getOption().isPerNoteSession()) {
return SHARED_SESSION;
} else {
return noteId;
}
}
private List<Interpreter> createOrGetInterpreterList(InterpreterSetting setting) {
InterpreterGroup interpreterGroup = setting.getInterpreterGroup();
synchronized (interpreterGroup) {
String key = getInterpreterGroupKey(setting);
if (!interpreterGroup.containsKey(key)) {
factory.createInterpretersForNote(setting, key);
}
return interpreterGroup.get(getInterpreterGroupKey(setting));
}
}
public void close() {
// close interpreters in this note session
List<InterpreterSetting> settings = this.getInterpreterSettings();
if (settings == null || settings.size() == 0) {
return;
}
for (InterpreterSetting setting : settings) {
factory.removeInterpretersForNote(setting, noteId);
}
}
public Interpreter get(String replName) {
List<InterpreterSetting> settings = getInterpreterSettings();
@ -81,7 +113,9 @@ public class NoteInterpreterLoader {
}
if (replName == null || replName.trim().length() == 0) {
return settings.get(0).getInterpreterGroup().getFirst();
// get default settings (first available)
InterpreterSetting defaultSettings = settings.get(0);
return createOrGetInterpreterList(defaultSettings).get(0);
}
if (Interpreter.registeredInterpreters == null) {
@ -104,43 +138,47 @@ public class NoteInterpreterLoader {
String interpreterClassName = registeredInterpreter.getClassName();
for (InterpreterSetting setting : settings) {
InterpreterGroup intpGroup = setting.getInterpreterGroup();
for (Interpreter interpreter : intpGroup) {
if (interpreterClassName.equals(interpreter.getClassName())) {
return interpreter;
if (registeredInterpreter.getGroup().equals(setting.getGroup())) {
List<Interpreter> intpGroup = createOrGetInterpreterList(setting);
for (Interpreter interpreter : intpGroup) {
if (interpreterClassName.equals(interpreter.getClassName())) {
return interpreter;
}
}
}
}
throw new InterpreterException(replName + " interpreter not found");
} else {
// first assume replName is 'name' of interpreter. ('groupName' is ommitted)
// search 'name' from first (default) interpreter group
InterpreterGroup intpGroup = settings.get(0).getInterpreterGroup();
for (Interpreter interpreter : intpGroup) {
RegisteredInterpreter intp = Interpreter
.findRegisteredInterpreterByClassName(interpreter.getClassName());
if (intp == null) {
continue;
InterpreterSetting defaultSetting = settings.get(0);
Interpreter.RegisteredInterpreter registeredInterpreter =
Interpreter.registeredInterpreters.get(defaultSetting.getGroup() + "." + replName);
if (registeredInterpreter != null) {
List<Interpreter> interpreters = createOrGetInterpreterList(defaultSetting);
for (Interpreter interpreter : interpreters) {
RegisteredInterpreter intp =
Interpreter.findRegisteredInterpreterByClassName(interpreter.getClassName());
if (intp == null) {
continue;
}
if (intp.getName().equals(replName)) {
return interpreter;
}
}
if (intp.getName().equals(replName)) {
return interpreter;
}
throw new InterpreterException(
defaultSetting.getGroup() + "." + replName + " interpreter not found");
}
// next, assume replName is 'group' of interpreter ('name' is ommitted)
// search interpreter group and return first interpreter.
for (InterpreterSetting setting : settings) {
intpGroup = setting.getInterpreterGroup();
Interpreter interpreter = intpGroup.get(0);
RegisteredInterpreter intp = Interpreter
.findRegisteredInterpreterByClassName(interpreter.getClassName());
if (intp == null) {
continue;
}
if (intp.getGroup().equals(replName)) {
return interpreter;
if (setting.getGroup().equals(replName)) {
List<Interpreter> interpreters = createOrGetInterpreterList(setting);
return interpreters.get(0);
}
}
}

View file

@ -244,7 +244,8 @@ public class Notebook {
Note note = getNote(id);
if (note != null) {
note.getNoteReplLoader().setInterpreters(interpreterSettingIds);
replFactory.putNoteInterpreterSettingBinding(id, interpreterSettingIds);
// comment out while note.getNoteReplLoader().setInterpreters(...) do the same
// replFactory.putNoteInterpreterSettingBinding(id, interpreterSettingIds);
}
}
@ -278,6 +279,7 @@ public class Notebook {
synchronized (notes) {
note = notes.remove(id);
}
replFactory.removeNoteInterpreterSettingBinding(id);
notebookIndex.deleteIndexDocs(note);
// remove from all interpreter instance's angular object registry

View file

@ -27,6 +27,7 @@ import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.resource.ResourcePool;
import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.JobListener;
import org.apache.zeppelin.scheduler.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -261,7 +262,17 @@ public class Paragraph extends Job implements Serializable, Cloneable {
@Override
protected boolean jobAbort() {
Interpreter repl = getRepl(getRequiredReplName());
Job job = repl.getScheduler().removeFromWaitingQueue(getId());
if (repl == null) {
// when interpreters are already destroyed
return true;
}
Scheduler scheduler = repl.getScheduler();
if (scheduler == null) {
return true;
}
Job job = scheduler.removeFromWaitingQueue(getId());
if (job != null) {
job.setStatus(Status.ABORT);
} else {

View file

@ -22,8 +22,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
@ -87,9 +86,12 @@ public class InterpreterFactoryTest {
@Test
public void testBasic() {
List<String> all = factory.getDefaultInterpreterSettingList();
InterpreterSetting setting = factory.get(all.get(0));
InterpreterGroup interpreterGroup = setting.getInterpreterGroup();
factory.createInterpretersForNote(setting, "session");
// get interpreter
Interpreter repl1 = factory.get(all.get(0)).getInterpreterGroup().getFirst();
Interpreter repl1 = interpreterGroup.get("session").get(0);
assertFalse(((LazyOpenInterpreter) repl1).isOpen());
repl1.interpret("repl1", context);
assertTrue(((LazyOpenInterpreter) repl1).isOpen());
@ -99,23 +101,14 @@ public class InterpreterFactoryTest {
// restart interpreter
factory.restart(all.get(0));
repl1 = factory.get(all.get(0)).getInterpreterGroup().getFirst();
assertFalse(((LazyOpenInterpreter) repl1).isOpen());
assertNull(setting.getInterpreterGroup().get("session"));
}
@Test
public void testFactoryDefaultList() throws IOException, RepositoryException {
// get default list from default setting
// get default settings
List<String> all = factory.getDefaultInterpreterSettingList();
assertEquals(2, all.size());
assertEquals(factory.get(all.get(0)).getInterpreterGroup().getFirst().getClassName(), "org.apache.zeppelin.interpreter.mock.MockInterpreter1");
// add setting
factory.add("a mock", "mock2", new LinkedList<Dependency>(), new InterpreterOption(false), new Properties());
all = factory.getDefaultInterpreterSettingList();
assertEquals(2, all.size());
assertEquals("mock1", factory.get(all.get(0)).getName());
assertEquals("a mock", factory.get(all.get(1)).getName());
}
@Test

View file

@ -45,7 +45,13 @@ public class MockInterpreter1 extends Interpreter{
@Override
public InterpreterResult interpret(String st, InterpreterContext context) {
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "repl1: "+st);
if ("getId".equals(st)) {
// get unique id of this interpreter instance
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "" + this.hashCode());
} else {
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "repl1: " + st);
}
}
@Override

View file

@ -16,8 +16,6 @@
*/
package org.apache.zeppelin.notebook;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
@ -36,6 +34,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class NoteInterpreterLoaderTest {
private File tmpDir;
@ -92,6 +92,43 @@ public class NoteInterpreterLoaderTest {
assertEquals("org.apache.zeppelin.interpreter.mock.MockInterpreter1", loader.get("group1.mock1").getClassName());
assertEquals("org.apache.zeppelin.interpreter.mock.MockInterpreter11", loader.get("group1.mock11").getClassName());
assertEquals("org.apache.zeppelin.interpreter.mock.MockInterpreter2", loader.get("group2.mock2").getClassName());
loader.close();
}
@Test
public void testNoteSession() throws IOException {
NoteInterpreterLoader loaderA = new NoteInterpreterLoader(factory);
loaderA.setNoteId("noteA");
loaderA.setInterpreters(factory.getDefaultInterpreterSettingList());
loaderA.getInterpreterSettings().get(0).getOption().setPerNoteSession(true);
NoteInterpreterLoader loaderB = new NoteInterpreterLoader(factory);
loaderB.setNoteId("noteB");
loaderB.setInterpreters(factory.getDefaultInterpreterSettingList());
loaderB.getInterpreterSettings().get(0).getOption().setPerNoteSession(true);
// interpreters are not created before accessing it
assertNull(loaderA.getInterpreterSettings().get(0).getInterpreterGroup().get("noteA"));
assertNull(loaderB.getInterpreterSettings().get(0).getInterpreterGroup().get("noteB"));
// per note session interpreter instance in the same interpreter process
assertTrue(
loaderA.get(null).getInterpreterGroup().getRemoteInterpreterProcess() ==
loaderB.get(null).getInterpreterGroup().getRemoteInterpreterProcess());
// interpreters are created after accessing it
assertNotNull(loaderA.getInterpreterSettings().get(0).getInterpreterGroup().get("noteA"));
assertNotNull(loaderB.getInterpreterSettings().get(0).getInterpreterGroup().get("noteB"));
// when
loaderA.close();
loaderB.close();
// interpreters are destroyed after close
assertNull(loaderA.getInterpreterSettings().get(0).getInterpreterGroup().get("noteA"));
assertNull(loaderB.getInterpreterSettings().get(0).getInterpreterGroup().get("noteB"));
}
private void delete(File file){

Some files were not shown because too many files have changed in this diff Show more