Skip to content

Commit 52ea80f

Browse files
authored
[ZEPPELIN-6163] HBase interpreter supports hbase-2.x
### What is this PR for? Currently in Zeppelin HBase interpreter does not support HBase 2.x ruby syntax. This patch added hbase-2.x support. This closes ZEPPELIN-6163 ### What type of PR is it? Improvement ### Todos ### 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] [ZEPPELIN-6163](https://issues.apache.org/jira/browse/ZEPPELIN-6163) ### How should this be tested? * Strongly recommended: add automated unit tests for any new or changed behavior * Outline any manual steps to test the PR here. Compiled and tested manually. ### Screenshots (if appropriate) ### Questions: * Does the license files need to update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Closes #4908 from paul8263/hbase-2.x. Signed-off-by: Jongyoul Lee <[email protected]>
1 parent 033c436 commit 52ea80f

File tree

10 files changed

+339
-169
lines changed

10 files changed

+339
-169
lines changed

docs/interpreter/hbase.md

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,7 @@ limitations under the License.
2828
To get start with HBase, please see [HBase Quickstart](https://hbase.apache.org/book.html#quickstart).
2929

3030
## HBase release supported
31-
By default, Zeppelin is built against HBase 1.0.x releases. To work with HBase 1.1.x releases, use the following build command:
32-
33-
```bash
34-
# HBase 1.1.4
35-
./mvnw clean package -DskipTests -Phadoop-2.6 -Dhadoop.version=2.6.0 -P build-distr -Dhbase.hbase.version=1.1.4 -Dhbase.hadoop.version=2.6.0
36-
```
37-
38-
To work with HBase 1.2.0+, use the following build command:
39-
40-
```bash
41-
# HBase 1.2.0
42-
./mvnw clean package -DskipTests -Phadoop-2.6 -Dhadoop.version=2.6.0 -P build-distr -Dhbase.hbase.version=1.2.0 -Dhbase.hadoop.version=2.6.0
43-
```
31+
Zeppelin is built against HBase 1.x and 2.x releases.
4432

4533
## Configuration
4634

@@ -55,16 +43,6 @@ To work with HBase 1.2.0+, use the following build command:
5543
<td>/usr/lib/hbase</td>
5644
<td>Installation directory of HBase, defaults to HBASE_HOME in environment</td>
5745
</tr>
58-
<tr>
59-
<td>hbase.ruby.sources</td>
60-
<td>lib/ruby</td>
61-
<td>Path to Ruby scripts relative to 'hbase.home'</td>
62-
</tr>
63-
<tr>
64-
<td>zeppelin.hbase.test.mode</td>
65-
<td>false</td>
66-
<td>Disable checks for unit and manual tests</td>
67-
</tr>
6846
</table>
6947

7048
If you want to connect to HBase running on a cluster, you'll need to follow the next step.

hbase/pom.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,16 @@
3333
<properties>
3434
<!--library versions-->
3535
<interpreter.name>hbase</interpreter.name>
36-
<jruby.version>1.6.8</jruby.version>
3736
</properties>
3837

3938
<dependencies>
4039
<dependency>
41-
<groupId>org.jruby</groupId>
42-
<artifactId>jruby-complete</artifactId>
43-
<version>${jruby.version}</version>
40+
<groupId>commons-io</groupId>
41+
<artifactId>commons-io</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.apache.commons</groupId>
45+
<artifactId>commons-lang3</artifactId>
4446
</dependency>
4547
</dependencies>
4648

hbase/src/main/java/org/apache/zeppelin/hbase/HbaseInterpreter.java

Lines changed: 110 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -14,118 +14,135 @@
1414

1515
package org.apache.zeppelin.hbase;
1616

17-
import org.jruby.embed.LocalContextScope;
18-
import org.jruby.embed.ScriptingContainer;
19-
import org.slf4j.Logger;
20-
import org.slf4j.LoggerFactory;
21-
17+
import java.io.ByteArrayOutputStream;
2218
import java.io.File;
23-
import java.io.FileInputStream;
2419
import java.io.IOException;
25-
import java.io.StringWriter;
20+
import java.nio.file.Files;
2621
import java.nio.file.Path;
2722
import java.nio.file.Paths;
28-
import java.util.List;
23+
import java.util.HashMap;
24+
import java.util.Map;
2925
import java.util.Properties;
3026

27+
import org.apache.commons.exec.CommandLine;
28+
import org.apache.commons.exec.DefaultExecutor;
29+
import org.apache.commons.exec.ExecuteException;
30+
import org.apache.commons.exec.ExecuteWatchdog;
31+
import org.apache.commons.exec.Executor;
32+
import org.apache.commons.exec.PumpStreamHandler;
33+
import org.apache.commons.io.FileUtils;
34+
import org.apache.commons.lang3.StringUtils;
3135
import org.apache.zeppelin.interpreter.Interpreter;
3236
import org.apache.zeppelin.interpreter.InterpreterContext;
3337
import org.apache.zeppelin.interpreter.InterpreterException;
3438
import org.apache.zeppelin.interpreter.InterpreterResult;
35-
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
3639
import org.apache.zeppelin.scheduler.Scheduler;
3740
import org.apache.zeppelin.scheduler.SchedulerFactory;
41+
import org.slf4j.Logger;
42+
import org.slf4j.LoggerFactory;
3843

3944
/**
40-
* Support for HBase Shell. All the commands documented here
41-
* http://hbase.apache.org/book.html#shell is supported.
42-
*
43-
* Requirements:
44-
* HBase Shell should be installed on the same machine. To be more specific, the following dir.
45-
* should be available: https://github.com/apache/hbase/tree/master/hbase-shell/src/main/ruby
46-
* HBase Shell should be able to connect to the HBase cluster from terminal. This makes sure
47-
* that the client is configured properly.
48-
*
49-
* The interpreter takes 3 config parameters:
50-
* hbase.home: Root directory where HBase is installed. Default is /usr/lib/hbase/
51-
* hbase.ruby.sources: Dir where shell ruby code is installed.
52-
* Path is relative to hbase.home. Default: lib/ruby
53-
* zeppelin.hbase.test.mode: (Testing only) Disable checks for unit and manual tests. Default: false
45+
* HBase interpreter. It uses the hbase shell to interpret the commands.
5446
*/
5547
public class HbaseInterpreter extends Interpreter {
48+
private static final Logger LOGGER = LoggerFactory.getLogger(HbaseInterpreter.class);
49+
5650
public static final String HBASE_HOME = "hbase.home";
57-
public static final String HBASE_RUBY_SRC = "hbase.ruby.sources";
58-
public static final String HBASE_TEST_MODE = "zeppelin.hbase.test.mode";
5951

60-
private static final Logger LOGGER = LoggerFactory.getLogger(HbaseInterpreter.class);
61-
private ScriptingContainer scriptingContainer;
52+
private static final Path TEMP_FOLDER = Paths.get(System.getProperty("java.io.tmpdir"),
53+
"zeppelin-hbase-scripts");
6254

63-
private StringWriter writer;
55+
private Map<String, Executor> runningProcesses = new HashMap<>();
6456

65-
public HbaseInterpreter(Properties property) {
66-
super(property);
57+
private Map<String, File> tempFiles = new HashMap<>();
58+
59+
private static final int SIGTERM_CODE = 143;
60+
61+
private long commandTimeout = 60000;
62+
63+
public HbaseInterpreter(Properties properties) {
64+
super(properties);
6765
}
6866

6967
@Override
7068
public void open() throws InterpreterException {
71-
this.scriptingContainer = new ScriptingContainer(LocalContextScope.SINGLETON);
72-
this.writer = new StringWriter();
73-
scriptingContainer.setOutput(this.writer);
74-
75-
if (!Boolean.parseBoolean(getProperty(HBASE_TEST_MODE))) {
76-
String hbaseHome = getProperty(HBASE_HOME);
77-
String rubySrc = getProperty(HBASE_RUBY_SRC);
78-
Path absRubySrc = Paths.get(hbaseHome, rubySrc).toAbsolutePath();
79-
80-
LOGGER.info("Home:" + hbaseHome);
81-
LOGGER.info("Ruby Src:" + rubySrc);
82-
83-
File f = absRubySrc.toFile();
84-
if (!f.exists() || !f.isDirectory()) {
85-
throw new InterpreterException("HBase ruby sources is not available at '" + absRubySrc
86-
+ "'");
87-
}
88-
89-
LOGGER.info("Absolute Ruby Source:" + absRubySrc.toString());
90-
// hirb.rb:41 requires the following system properties to be set.
91-
Properties sysProps = System.getProperties();
92-
sysProps.setProperty(HBASE_RUBY_SRC, absRubySrc.toString());
93-
94-
Path absHirbPath = Paths.get(hbaseHome, "bin/hirb.rb");
95-
try {
96-
FileInputStream fis = new FileInputStream(absHirbPath.toFile());
97-
this.scriptingContainer.runScriptlet(fis, "hirb.rb");
98-
fis.close();
99-
} catch (IOException e) {
100-
throw new InterpreterException(e.getCause());
101-
}
102-
}
69+
// Do nothing
10370
}
10471

10572
@Override
10673
public void close() {
107-
if (this.scriptingContainer != null) {
108-
this.scriptingContainer.terminate();
109-
}
74+
runningProcesses.clear();
75+
runningProcesses = null;
76+
tempFiles.clear();
77+
tempFiles = null;
11078
}
11179

11280
@Override
113-
public InterpreterResult interpret(String cmd, InterpreterContext interpreterContext) {
81+
public InterpreterResult interpret(String st, InterpreterContext context) {
82+
LOGGER.debug("Run HBase shell script: {}", st);
83+
84+
if (StringUtils.isEmpty(st)) {
85+
return new InterpreterResult(InterpreterResult.Code.SUCCESS);
86+
}
87+
88+
String paragraphId = context.getParagraphId();
89+
final File scriptFile;
90+
try {
91+
// Write script in a temporary file
92+
// The script is enriched with extensions
93+
scriptFile = createTempFile(paragraphId);
94+
FileUtils.write(scriptFile, st + "\nexit");
95+
} catch (IOException e) {
96+
LOGGER.error("Can not write script in temp file", e);
97+
return new InterpreterResult(InterpreterResult.Code.ERROR, e.getMessage());
98+
}
99+
100+
InterpreterResult result = new InterpreterResult(InterpreterResult.Code.SUCCESS);
101+
102+
final DefaultExecutor executor = new DefaultExecutor();
103+
final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
104+
105+
executor.setStreamHandler(new PumpStreamHandler(context.out, errorStream));
106+
executor.setWatchdog(new ExecuteWatchdog(commandTimeout));
107+
108+
String hbaseCmdPath = Paths.get(getProperty(HBASE_HOME), "bin", "hbase").toString();
109+
final CommandLine cmdLine = CommandLine.parse(hbaseCmdPath);
110+
cmdLine.addArgument("shell", false);
111+
cmdLine.addArgument(scriptFile.getAbsolutePath(), false);
112+
114113
try {
115-
LOGGER.info(cmd);
116-
this.writer.getBuffer().setLength(0);
117-
this.scriptingContainer.runScriptlet(cmd);
118-
this.writer.flush();
119-
LOGGER.debug(writer.toString());
120-
return new InterpreterResult(InterpreterResult.Code.SUCCESS, writer.getBuffer().toString());
121-
} catch (Throwable t) {
122-
LOGGER.error("Can not run '" + cmd + "'", t);
123-
return new InterpreterResult(InterpreterResult.Code.ERROR, t.getMessage());
114+
executor.execute(cmdLine);
115+
runningProcesses.put(paragraphId, executor);
116+
} catch (ExecuteException e) {
117+
LOGGER.error("Can not run script in paragraph {}", paragraphId, e);
118+
119+
final int exitValue = e.getExitValue();
120+
InterpreterResult.Code code = InterpreterResult.Code.ERROR;
121+
String msg = errorStream.toString();
122+
123+
if (exitValue == SIGTERM_CODE) {
124+
code = InterpreterResult.Code.INCOMPLETE;
125+
msg = msg + "Paragraph received a SIGTERM.\n";
126+
LOGGER.info("The paragraph {} stopped executing: {}", paragraphId, msg);
127+
}
128+
129+
msg += "ExitValue: " + exitValue;
130+
result = new InterpreterResult(code, msg);
131+
} catch (IOException e) {
132+
LOGGER.error("Can not run script in paragraph {}", paragraphId, e);
133+
result = new InterpreterResult(InterpreterResult.Code.ERROR, e.getMessage());
134+
} finally {
135+
deleteTempFile(paragraphId);
136+
stopProcess(paragraphId);
124137
}
138+
return result;
125139
}
126140

127141
@Override
128-
public void cancel(InterpreterContext context) {}
142+
public void cancel(InterpreterContext context) {
143+
stopProcess(context.getParagraphId());
144+
deleteTempFile(context.getParagraphId());
145+
}
129146

130147
@Override
131148
public FormType getFormType() {
@@ -143,30 +160,27 @@ public Scheduler getScheduler() {
143160
HbaseInterpreter.class.getName() + this.hashCode());
144161
}
145162

146-
@Override
147-
public List<InterpreterCompletion> completion(String buf, int cursor,
148-
InterpreterContext interpreterContext) {
149-
return null;
163+
private void stopProcess(String paragraphId) {
164+
Executor executor = runningProcesses.remove(paragraphId);
165+
if (null != executor) {
166+
final ExecuteWatchdog watchdog = executor.getWatchdog();
167+
watchdog.destroyProcess();
168+
}
150169
}
151170

152-
private static String getSystemDefault(
153-
String envName,
154-
String propertyName,
155-
String defaultValue) {
156-
157-
if (envName != null && !envName.isEmpty()) {
158-
String envValue = System.getenv().get(envName);
159-
if (envValue != null) {
160-
return envValue;
161-
}
171+
private File createTempFile(String paragraphId) throws IOException {
172+
if (!Files.exists(TEMP_FOLDER)) {
173+
Files.createDirectory(TEMP_FOLDER);
162174
}
175+
File temp = Files.createTempFile(TEMP_FOLDER, paragraphId, ".txt").toFile();
176+
tempFiles.put(paragraphId, temp);
177+
return temp;
178+
}
163179

164-
if (propertyName != null && !propertyName.isEmpty()) {
165-
String propValue = System.getProperty(propertyName);
166-
if (propValue != null) {
167-
return propValue;
168-
}
180+
private void deleteTempFile(String paragraphId) {
181+
File tmpFile = tempFiles.remove(paragraphId);
182+
if (null != tmpFile) {
183+
FileUtils.deleteQuietly(tmpFile);
169184
}
170-
return defaultValue;
171185
}
172186
}

hbase/src/main/resources/interpreter-setting.json

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,6 @@
1010
"defaultValue": "/usr/lib/hbase/",
1111
"description": "Installation directory of HBase",
1212
"type": "string"
13-
},
14-
"hbase.ruby.sources": {
15-
"propertyName": "hbase.ruby.sources",
16-
"defaultValue": "lib/ruby",
17-
"description": "Path to Ruby scripts relative to 'hbase.home'",
18-
"type": "string"
19-
},
20-
"zeppelin.hbase.test.mode": {
21-
"propertyName": "zeppelin.hbase.test.mode",
22-
"defaultValue": false,
23-
"description": "Disable checks for unit and manual tests",
24-
"type": "checkbox"
2513
}
2614
},
2715
"editor": {

hbase/src/test/java/org/apache/zeppelin/hbase/HbaseInterpreterTest.java

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@
1414

1515
package org.apache.zeppelin.hbase;
1616

17-
import static org.junit.jupiter.api.Assertions.assertEquals;
18-
import static org.junit.jupiter.api.Assertions.assertNotNull;
19-
20-
import java.util.Properties;
21-
2217
import org.apache.zeppelin.interpreter.InterpreterException;
23-
import org.apache.zeppelin.interpreter.InterpreterResult;
2418
import org.junit.jupiter.api.BeforeAll;
2519
import org.junit.jupiter.api.Test;
2620

21+
import java.util.Properties;
22+
23+
import static org.junit.jupiter.api.Assertions.assertNotNull;
24+
2725
/**
2826
* Tests for HBase Interpreter.
2927
*/
@@ -34,8 +32,6 @@ public class HbaseInterpreterTest {
3432
public static void setUp() throws NullPointerException, InterpreterException {
3533
Properties properties = new Properties();
3634
properties.put("hbase.home", "");
37-
properties.put("hbase.ruby.sources", "");
38-
properties.put("zeppelin.hbase.test.mode", "true");
3935

4036
hbaseInterpreter = new HbaseInterpreter(properties);
4137
hbaseInterpreter.open();
@@ -45,28 +41,4 @@ public static void setUp() throws NullPointerException, InterpreterException {
4541
void newObject() {
4642
assertNotNull(hbaseInterpreter);
4743
}
48-
49-
@Test
50-
void putsTest() {
51-
InterpreterResult result = hbaseInterpreter.interpret("puts \"Hello World\"", null);
52-
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
53-
assertEquals(InterpreterResult.Type.TEXT, result.message().get(0).getType());
54-
assertEquals("Hello World\n", result.message().get(0).getData());
55-
}
56-
57-
public void putsLoadPath() {
58-
InterpreterResult result = hbaseInterpreter.interpret(
59-
"require 'two_power'; puts twoToThePowerOf(4)", null);
60-
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
61-
assertEquals(InterpreterResult.Type.TEXT, result.message().get(0).getType());
62-
assertEquals("16\n", result.message().get(0).getData());
63-
}
64-
65-
@Test
66-
void testException() {
67-
InterpreterResult result = hbaseInterpreter.interpret("plot practical joke", null);
68-
assertEquals(InterpreterResult.Code.ERROR, result.code());
69-
assertEquals("(NameError) undefined local variable or method `joke' for main:Object",
70-
result.message().get(0).getData());
71-
}
7244
}

0 commit comments

Comments
 (0)