Skip to content

Commit ef3f588

Browse files
committed
[ZEPPELIN-6163] HBase interpreter supports hbase-2.x
1 parent db0ea40 commit ef3f588

File tree

5 files changed

+118
-169
lines changed

5 files changed

+118
-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: 107 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -14,118 +14,131 @@
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;
26-
import java.nio.file.Path;
20+
import java.nio.file.Files;
2721
import java.nio.file.Paths;
28-
import java.util.List;
22+
import java.util.HashMap;
23+
import java.util.Map;
2924
import java.util.Properties;
3025

26+
import org.apache.commons.exec.CommandLine;
27+
import org.apache.commons.exec.DefaultExecutor;
28+
import org.apache.commons.exec.ExecuteException;
29+
import org.apache.commons.exec.ExecuteWatchdog;
30+
import org.apache.commons.exec.Executor;
31+
import org.apache.commons.exec.PumpStreamHandler;
32+
import org.apache.commons.io.FileUtils;
33+
import org.apache.commons.lang3.StringUtils;
3134
import org.apache.zeppelin.interpreter.Interpreter;
3235
import org.apache.zeppelin.interpreter.InterpreterContext;
3336
import org.apache.zeppelin.interpreter.InterpreterException;
3437
import org.apache.zeppelin.interpreter.InterpreterResult;
35-
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
3638
import org.apache.zeppelin.scheduler.Scheduler;
3739
import org.apache.zeppelin.scheduler.SchedulerFactory;
40+
import org.slf4j.Logger;
41+
import org.slf4j.LoggerFactory;
3842

3943
/**
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
44+
* HBase interpreter. It uses the hbase shell to interpret the commands.
5445
*/
5546
public class HbaseInterpreter extends Interpreter {
47+
private static final Logger LOGGER = LoggerFactory.getLogger(HbaseInterpreter.class);
48+
5649
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";
5950

60-
private static final Logger LOGGER = LoggerFactory.getLogger(HbaseInterpreter.class);
61-
private ScriptingContainer scriptingContainer;
51+
private Map<String, Executor> runningProcesses = new HashMap<>();
6252

63-
private StringWriter writer;
53+
private Map<String, File> tempFiles = new HashMap<>();
6454

65-
public HbaseInterpreter(Properties property) {
66-
super(property);
55+
private static final int SIGTERM_CODE = 143;
56+
57+
private long commandTimeout = 60000;
58+
59+
public HbaseInterpreter(Properties properties) {
60+
super(properties);
6761
}
6862

6963
@Override
7064
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-
}
65+
// Do nothing
10366
}
10467

10568
@Override
10669
public void close() {
107-
if (this.scriptingContainer != null) {
108-
this.scriptingContainer.terminate();
109-
}
70+
runningProcesses.clear();
71+
runningProcesses = null;
72+
tempFiles.clear();
73+
tempFiles = null;
11074
}
11175

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

127137
@Override
128-
public void cancel(InterpreterContext context) {}
138+
public void cancel(InterpreterContext context) {
139+
stopProcess(context.getParagraphId());
140+
deleteTempFile(context.getParagraphId());
141+
}
129142

130143
@Override
131144
public FormType getFormType() {
@@ -143,30 +156,26 @@ public Scheduler getScheduler() {
143156
HbaseInterpreter.class.getName() + this.hashCode());
144157
}
145158

146-
@Override
147-
public List<InterpreterCompletion> completion(String buf, int cursor,
148-
InterpreterContext interpreterContext) {
149-
return null;
159+
private void stopProcess(String paragraphId) {
160+
if (runningProcesses.containsKey(paragraphId)) {
161+
final Executor executor = runningProcesses.get(paragraphId);
162+
final ExecuteWatchdog watchdog = executor.getWatchdog();
163+
watchdog.destroyProcess();
164+
runningProcesses.remove(paragraphId);
165+
}
150166
}
151167

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-
}
162-
}
168+
private File createTempFile(String paragraphId) throws IOException {
169+
File temp = Files.createTempFile(
170+
Paths.get(System.getProperty("java.io.tmpdir"), "zeppelin-hbase-scripts"), paragraphId, ".txt").toFile();
171+
tempFiles.put(paragraphId, temp);
172+
return temp;
173+
}
163174

164-
if (propertyName != null && !propertyName.isEmpty()) {
165-
String propValue = System.getProperty(propertyName);
166-
if (propValue != null) {
167-
return propValue;
168-
}
175+
private void deleteTempFile(String paragraphId) {
176+
File tmpFile = tempFiles.remove(paragraphId);
177+
if (null != tmpFile) {
178+
FileUtils.deleteQuietly(tmpFile);
169179
}
170-
return defaultValue;
171180
}
172181
}

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)