This is an automated email from the ASF dual-hosted git repository. zjffdu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push: new f4ca1a2 [ZEPPELIN-4571] Add MongoDB interpreter for Zeppelin f4ca1a2 is described below commit f4ca1a27af0ada77d7bdd1f0d1b4e44a9539a172 Author: Tang Xiao <tangxc1...@126.com> AuthorDate: Sun Jan 19 17:44:15 2020 +0800 [ZEPPELIN-4571] Add MongoDB interpreter for Zeppelin ### What is this PR for? add interpreter for mongodb ### What type of PR is it? [Feature] ### Todos * [ ] - Task ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-4571 ### How should this be tested? unit test supplied ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? Yes,Updated Author: Tang Xiao <tangxc1...@126.com> Closes #3612 from tokenian/mongo-interpreter and squashes the following commits: 48b95efbb [Tang Xiao] [ZEPPELIN-4571]add mongodb interpreter for convience --- .travis.yml | 2 +- docs/_includes/themes/zeppelin/_navigation.html | 1 + .../zeppelin/img/docs-img/mongo-examples.png | Bin 0 -> 310717 bytes .../img/docs-img/mongo-interpreter-install.png | Bin 0 -> 173720 bytes .../img/docs-img/mongo-interpreter-monitor.png | Bin 0 -> 311948 bytes docs/interpreter/mongodb.md | 102 ++++++++++ mongodb/README.md | 8 + mongodb/pom.xml | 60 ++++++ .../zeppelin/mongodb/MongoDbInterpreter.java | 206 +++++++++++++++++++++ .../src/main/resources/interpreter-setting.json | 84 +++++++++ mongodb/src/main/resources/shell_extension.js | 121 ++++++++++++ .../zeppelin/mongodb/MongoDbInterpreterTest.java | 159 ++++++++++++++++ pom.xml | 1 + zeppelin-web/bower.json | 3 +- 14 files changed, 745 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 26295ba..ec5e027 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,7 +51,7 @@ services: env: global: # Interpreters does not required by zeppelin-server integration tests - - INTERPRETERS='!beam,!hbase,!pig,!jdbc,!file,!flink,!ignite,!kylin,!lens,!cassandra,!elasticsearch,!bigquery,!alluxio,!scio,!livy,!groovy,!sap,!java,!geode,!neo4j,!hazelcastjet,!submarine,!sparql' + - INTERPRETERS='!beam,!hbase,!pig,!jdbc,!file,!flink,!ignite,!kylin,!lens,!cassandra,!elasticsearch,!bigquery,!alluxio,!scio,!livy,!groovy,!sap,!java,!geode,!neo4j,!hazelcastjet,!submarine,!sparql,!mongodb' matrix: include: diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html index f19dc30..0a2563f 100644 --- a/docs/_includes/themes/zeppelin/_navigation.html +++ b/docs/_includes/themes/zeppelin/_navigation.html @@ -149,6 +149,7 @@ <li><a href="{{BASE_PATH}}/interpreter/lens.html">Lens</a></li> <li><a href="{{BASE_PATH}}/interpreter/livy.html">Livy</a></li> <li><a href="{{BASE_PATH}}/interpreter/markdown.html">Markdown</a></li> + <li><a href="{{BASE_PATH}}/interpreter/mongodb.html">MongoDB</a></li> <li><a href="{{BASE_PATH}}/interpreter/neo4j.html">Neo4j</a></li> <li><a href="{{BASE_PATH}}/interpreter/pig.html">Pig</a></li> <li><a href="{{BASE_PATH}}/interpreter/postgresql.html">Postgresql, HAWQ</a></li> diff --git a/docs/assets/themes/zeppelin/img/docs-img/mongo-examples.png b/docs/assets/themes/zeppelin/img/docs-img/mongo-examples.png new file mode 100644 index 0000000..0e111c9 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/mongo-examples.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png new file mode 100644 index 0000000..c24834d Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png new file mode 100644 index 0000000..68cd0db Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png differ diff --git a/docs/interpreter/mongodb.md b/docs/interpreter/mongodb.md new file mode 100644 index 0000000..5561ee6 --- /dev/null +++ b/docs/interpreter/mongodb.md @@ -0,0 +1,102 @@ +--- +layout: page +title: "MongoDB Interpreter for Apache Zeppelin" +description: "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era." +group: interpreter +--- +<!-- +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 %} + +# MongoDB interpreter for Apache Zeppelin + +<div id="toc"></div> + +## Overview +[MongoDB](https://www.mongodb.com/) is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. +This interpreter use mongo shell to execute [scripts](https://docs.mongodb.com/manual/tutorial/write-scripts-for-the-mongo-shell/) +Use mongo-shell `JavaScript` to analyze data as you need. +## Installing AND Configuration +First, you need to install mongo shell with Zeppelin in the same machine. +If you use mac with brew, follow this instructions. +``` +brew tap mongodb/brew +brew install mongodb/brew/mongodb-community-shell +``` +Or you can follow this [mongo shell](https://docs.mongodb.com/manual/mongo/) +Second, create mongodb interpreter in Zeppelin. + +<table class="table-configuration"> + <tr> + <th>Name</th> + <th>Default Value</th> + <th>Description</th> + </tr> + <tr> + <td>mongo.shell.path</td> + <td>mongo</td> + <td>MongoDB shell local path. <br/> Use `which mongo` to get local path in linux or mac.</td> + </tr> + <tr> + <td>mongo.shell.command.table.limit</td> + <td>1000</td> + <td>Limit of documents displayed in a table. <br/> Use table function when get data from mongodb</td> + </tr> + <tr> + <td>mongo.shell.command.timeout</td> + <td>60000</td> + <td>MongoDB shell command timeout in millisecond</td> + </tr> + <tr> + <td>mongo.server.host</td> + <td>localhost</td> + <td>MongoDB server host to connect to</td> + </tr> + <tr> + <td>mongo.server.port</td> + <td>27017</td> + <td>MongoDB server port to connect to</td> + </tr> + <tr> + <td>mongo.server.database</td> + <td>test</td> + <td>MongoDB database name</td> + </tr> + <tr> + <td>mongo.server.authentdatabase</td> + <td></td> + <td>MongoDB database name for authentication</td> + </tr> + <tr> + <td>mongo.server.username</td> + <td></td> + <td>Username for authentication</td> + </tr> + <tr> + <td>mongo.server.password</td> + <td></td> + <td>Password for authentication</td> + </tr> + <tr> + <td>mongo.interpreter.concurrency.max</td> + <td>10</td> + <td>Max count of scheduler concurrency</td> + </tr> +</table> +## Examples +The following example demonstrates the basic usage of MongoDB in a Zeppelin notebook. + +Or you can monitor stats of mongodb collections. + + diff --git a/mongodb/README.md b/mongodb/README.md new file mode 100644 index 0000000..7679864 --- /dev/null +++ b/mongodb/README.md @@ -0,0 +1,8 @@ +# Overview +MongoDB interpreter for Apache Zeppelin. Thanksgiving to [bbonnin/zeppelin-mongodb-interpreter](https://github.com/bbonnin/zeppelin-mongodb-interpreter). +I found bbonnin's mongodb interpreter was not working with newest zeppelin version, it has not been maintained for a long time. +so I forked this for those people who want to use mongodb in zeppelin. + +### Technical overview +it use mongo shell to execute scripts.All you need to do is to configure mongodb interpreter, +and then study mongo aggregate functions. diff --git a/mongodb/pom.xml b/mongodb/pom.xml new file mode 100644 index 0000000..b3b4c1a --- /dev/null +++ b/mongodb/pom.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>zeppelin-interpreter-parent</artifactId> + <groupId>org.apache.zeppelin</groupId> + <version>0.9.0-SNAPSHOT</version> + <relativePath>../zeppelin-interpreter-parent/pom.xml</relativePath> + </parent> + + <groupId>org.apache.zeppelin</groupId> + <artifactId>zeppelin-mongodb</artifactId> + <packaging>jar</packaging> + <version>0.9.0-SNAPSHOT</version> + <name>Zeppelin: MongoDB interpreter</name> + + <properties> + <interpreter.name>mongodb</interpreter.name> + </properties> + + <build> + <plugins> + <plugin> + <artifactId>maven-enforcer-plugin</artifactId> + </plugin> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + </plugin> + <plugin> + <artifactId>maven-resources-plugin</artifactId> + </plugin> + <plugin> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + <plugin> + <artifactId>maven-checkstyle-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java b/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java new file mode 100644 index 0000000..97ff2da --- /dev/null +++ b/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java @@ -0,0 +1,206 @@ +/* + * 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.mongodb; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Scanner; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.Executor; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.interpreter.Interpreter; +import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResult.Code; +import org.apache.zeppelin.scheduler.Scheduler; +import org.apache.zeppelin.scheduler.SchedulerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * MongoDB interpreter. It uses the mongo shell to interpret the commands. + */ +public class MongoDbInterpreter extends Interpreter { + + private static final Logger LOGGER = LoggerFactory.getLogger(MongoDbInterpreter.class); + + private String shellExtension = StringUtils.EMPTY; + + private static final int SIGTERM_CODE = 143; + + private long commandTimeout = 60000; + + private String dbAddress; + + private int maxConcurrency = 10; + + private Map<String, Executor> runningProcesses = new HashMap<>(); + + public MongoDbInterpreter(Properties property) { + super(property); + } + + @Override + public void open() { + shellExtension = new Scanner(MongoDbInterpreter.class.getResourceAsStream("/shell_extension.js"), "UTF-8") + .useDelimiter("\\A").next(); + + commandTimeout = Long.parseLong(getProperty("mongo.shell.command.timeout")); + maxConcurrency = Integer.parseInt(getProperty("mongo.interpreter.concurrency.max")); + + dbAddress = getProperty("mongo.server.host") + ":" + getProperty("mongo.server.port"); + + prepareShellExtension(); + } + + @Override + public void close() { + runningProcesses.clear(); + runningProcesses = null; + } + + @Override + public FormType getFormType() { + return FormType.SIMPLE; + } + + @Override + public InterpreterResult interpret(String script, InterpreterContext context) { + LOGGER.debug("Run MongoDB script: {}", script); + + if (StringUtils.isEmpty(script)) { + return new InterpreterResult(Code.SUCCESS); + } + + String paragraphId = context.getParagraphId(); + // Write script in a temporary file + // The script is enriched with extensions + final File scriptFile = new File(getScriptFileName(paragraphId)); + try { + FileUtils.write(scriptFile, shellExtension + script); + } + catch (IOException e) { + LOGGER.error("Can not write script in temp file", e); + return new InterpreterResult(Code.ERROR, e.getMessage()); + } + + InterpreterResult result = new InterpreterResult(InterpreterResult.Code.SUCCESS); + + final DefaultExecutor executor = new DefaultExecutor(); + final ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + + executor.setStreamHandler(new PumpStreamHandler(context.out, errorStream)); + executor.setWatchdog(new ExecuteWatchdog(commandTimeout)); + + final CommandLine cmdLine = CommandLine.parse(getProperty("mongo.shell.path")); + cmdLine.addArgument("--quiet", false); + cmdLine.addArgument(dbAddress, false); + cmdLine.addArgument(scriptFile.getAbsolutePath(), false); + + try { + executor.execute(cmdLine); + runningProcesses.put(paragraphId, executor); + } + catch (ExecuteException e) { + LOGGER.error("Can not run script in paragraph {}", paragraphId, e); + + final int exitValue = e.getExitValue(); + Code code = Code.ERROR; + String msg = errorStream.toString(); + + if (exitValue == SIGTERM_CODE) { + code = Code.INCOMPLETE; + msg = msg + "Paragraph received a SIGTERM.\n"; + LOGGER.info("The paragraph {} stopped executing: {}", paragraphId, msg); + } + + msg += "ExitValue: " + exitValue; + result = new InterpreterResult(code, msg); + } + catch (IOException e) { + LOGGER.error("Can not run script in paragraph {}", paragraphId, e); + result = new InterpreterResult(Code.ERROR, e.getMessage()); + } + finally { + FileUtils.deleteQuietly(scriptFile); + stopProcess(paragraphId); + } + + return result; + } + + @Override + public int getProgress(InterpreterContext context) { + return 0; + } + + @Override + public void cancel(InterpreterContext context) { + stopProcess(context.getParagraphId()); + FileUtils.deleteQuietly(new File(getScriptFileName(context.getParagraphId()))); + } + + @Override + public Scheduler getScheduler() { + LOGGER.info("maxConcurrency is {}", maxConcurrency); + return SchedulerFactory.singleton().createOrGetParallelScheduler(MongoDbInterpreter.class.getName() + this.hashCode(), maxConcurrency); + } + + private String getScriptFileName(String paragraphId) { + return String.format("%s%s.js",getScriptDir(),paragraphId); + } + + private String getScriptDir(){ + String tmpProperty = System.getProperty("java.io.tmpdir"); + if (!tmpProperty.endsWith(File.separator)){ + tmpProperty += File.separator; + } + + return tmpProperty + "zeppelin-mongo-scripts" + File.separator; + } + + private void stopProcess(String paragraphId) { + if (runningProcesses.containsKey(paragraphId)) { + final Executor executor = runningProcesses.get(paragraphId); + final ExecuteWatchdog watchdog = executor.getWatchdog(); + watchdog.destroyProcess(); + runningProcesses.remove(paragraphId); + } + } + + /** + * use placeholders to replace properties + */ + private void prepareShellExtension(){ + shellExtension = shellExtension.replace("TABLE_LIMIT_PLACEHOLDER", getProperty("mongo.shell.command.table.limit")) + .replace("TARGET_DB_PLACEHOLDER", getProperty("mongo.server.database")) + .replace("USER_NAME_PLACEHOLDER", getProperty("mongo.server.username")) + .replace("PASSWORD_PLACEHOLDER", getProperty("mongo.server.password")) + .replace("AUTH_DB_PLACEHOLDER", getProperty("mongo.server.authenticationDatabase")); + } +} \ No newline at end of file diff --git a/mongodb/src/main/resources/interpreter-setting.json b/mongodb/src/main/resources/interpreter-setting.json new file mode 100644 index 0000000..706ba12 --- /dev/null +++ b/mongodb/src/main/resources/interpreter-setting.json @@ -0,0 +1,84 @@ +[ + { + "group": "mongodb", + "name": "mongodb", + "className": "org.apache.zeppelin.mongodb.MongoDbInterpreter", + "properties": { + "mongo.shell.path": { + "envName": null, + "propertyName": "mongo.shell.path", + "defaultValue": "mongo", + "description": "MongoDB shell local path", + "type": "string" + }, + "mongo.shell.command.table.limit": { + "envName": null, + "propertyName": "mongo.shell.command.table.limit", + "defaultValue": "1000", + "description": "Limit of documents displayed in a table", + "type": "number" + }, + "mongo.shell.command.timeout": { + "envName": null, + "propertyName": "mongo.shell.command.timeout", + "defaultValue": "60000", + "description": "MongoDB shell command timeout", + "type": "number" + }, + "mongo.server.host": { + "envName": null, + "propertyName": "mongo.server.host", + "defaultValue": "localhost", + "description": "MongoDB server host to connect to", + "type": "string" + }, + "mongo.server.port": { + "envName": null, + "propertyName": "mongo.server.port", + "defaultValue": "27017", + "description": "MongoDB server port to connect to", + "type": "number" + }, + "mongo.server.database": { + "envName": null, + "propertyName": "mongo.server.database", + "defaultValue": "test", + "description": "MongoDB database name", + "type": "string" + }, + "mongo.server.authenticationDatabase": { + "envName": null, + "propertyName": "mongo.server.authenticationDatabase", + "defaultValue": "", + "description": "MongoDB database name for authentication", + "type": "string" + }, + "mongo.server.username": { + "envName": null, + "propertyName": "mongo.server.username", + "defaultValue": "", + "description": "Username for authentication", + "type": "string" + }, + "mongo.server.password": { + "envName": null, + "propertyName": "mongo.server.password", + "defaultValue": "", + "description": "Password for authentication", + "type": "password" + }, + "mongo.interpreter.concurrency.max": { + "envName": null, + "propertyName": "mongo.interpreter.concurrency.max", + "defaultValue": "10", + "description": "Max count of scheduler concurrency", + "type": "number" + } + }, + "editor": { + "language": "javascript", + "editOnDblClick": false, + "completionKey": "TAB" + } + } +] diff --git a/mongodb/src/main/resources/shell_extension.js b/mongodb/src/main/resources/shell_extension.js new file mode 100644 index 0000000..b50f500 --- /dev/null +++ b/mongodb/src/main/resources/shell_extension.js @@ -0,0 +1,121 @@ +/* + * 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. + */ + +var tableLimit = TABLE_LIMIT_PLACEHOLDER; + +function flattenObject(obj, flattenArray) { + var toReturn = {}; + + for (var i in obj) { + if (!obj.hasOwnProperty(i)) continue; + + //if ((typeof obj[i]) == 'object') { + if (toString.call( obj[i] ) === '[object Object]' || + toString.call( obj[i] ) === '[object BSON]' || + (flattenArray && toString.call( obj[i] ) === '[object Array]')) { + var flatObject = flattenObject(obj[i]); + for (var x in flatObject) { + if (!flatObject.hasOwnProperty(x)) continue; + + toReturn[i + '.' + x] = flatObject[x]; + } + } else if (toString.call( obj[i] ) === '[object Array]') { + toReturn[i] = tojson(obj[i], null, true); + } else { + toReturn[i] = obj[i]; + } + } + return toReturn; +} + +function printTable(dbquery, fields, flattenArray) { + + var iterator = dbquery; + + if (toString.call( dbquery ) === '[object Array]') { + iterator = (function() { + var index = 0, + data = dbquery, + length = data.length; + + return { + next: function() { + if (!this.hasNext()) { + return null; + } + return data[index++]; + }, + hasNext: function() { + return index < length; + } + } + }()); + } + + // Flatten all the documents and get all the fields to build a table with all fields + var docs = []; + var createFieldSet = fields == null || fields.length == 0; + var fieldSet = fields ? [].concat(fields) : []; //new Set(fields); + + while (iterator.hasNext()) { + var doc = iterator.next(); + doc = flattenObject(doc, flattenArray); + docs.push(doc); + if (createFieldSet) { + for (var i in doc) { + if (doc.hasOwnProperty(i) && fieldSet.indexOf(i) === -1) { + fieldSet.push(i); + } + } + } + } + + fields = fieldSet; + + var header = "%table "; + fields.forEach(function (field) { header += field + "\t" }) + print(header.substring(0, header.length - 1)); + + docs.forEach(function (doc) { + var row = ""; + fields.forEach(function (field) { row += doc[field] + "\t" }) + print(row.substring(0, row.length - 1)); + }); +} + +DBQuery.prototype.table = function (fields, flattenArray) { + if (this._limit > tableLimit) { + this.limit(tableLimit); + } + printTable(this, fields, flattenArray); +}; + +DBCommandCursor.prototype.table = DBQuery.prototype.table; + +var userName = "USER_NAME_PLACEHOLDER"; +var password = "PASSWORD_PLACEHOLDER"; +var authDB = "AUTH_DB_PLACEHOLDER"; +var targetDB = "TARGET_DB_PLACEHOLDER"; + +if (userName){ + authDB = authDB || "admin"; + db = db.getSiblingDB(authDB); + db.auth(userName,password); +} + +db = db.getSiblingDB(targetDB); + diff --git a/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java b/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java new file mode 100644 index 0000000..7a39f7a --- /dev/null +++ b/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java @@ -0,0 +1,159 @@ +/* + * 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.mongodb; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Properties; +import java.util.Scanner; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterOutput; +import org.apache.zeppelin.interpreter.InterpreterOutputListener; +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResult.Code; +import org.apache.zeppelin.interpreter.InterpreterResultMessageOutput; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertEquals; +/** + * As there is no 'mongo' on the build platform, these tests simulates some basic behavior. + * + */ +public class MongoDbInterpreterTest implements InterpreterOutputListener { + + private static final String SHELL_EXTENSION = + new Scanner(MongoDbInterpreter.class.getResourceAsStream("/shell_extension.js"), "UTF-8") + .useDelimiter("\\A").next(); + + private static final boolean IS_WINDOWS = System.getProperty("os.name") + .startsWith("Windows"); + + private static final String MONGO_SHELL = System.getProperty("java.io.tmpdir") + + (System.getProperty("java.io.tmpdir").endsWith(File.separator) ? StringUtils.EMPTY : File.separator) + + "zeppelin-mongo-scripts"+ File.separator +"mongo-test." + (IS_WINDOWS ? "bat" : "sh"); + + private final Properties props = new Properties(); + private final MongoDbInterpreter interpreter = new MongoDbInterpreter(props); + private final InterpreterOutput out = new InterpreterOutput(this); + + private final InterpreterContext context = InterpreterContext.builder().setNoteId("test") + .setInterpreterOut(out).setNoteId("test").setParagraphId("test").build(); + + private ByteBuffer buffer; + + @BeforeClass + public static void setup() { + // Create a fake 'mongo' + final File mongoFile = new File(MONGO_SHELL); + try { + FileUtils.write(mongoFile, (IS_WINDOWS ? "@echo off\ntype \"%3%\"" : "cat \"$3\"")); + FileUtils.forceDeleteOnExit(mongoFile); + } + catch (IOException ex) { + System.out.println(ex.getMessage()); + } + } + + @Before + public void init() { + buffer = ByteBuffer.allocate(10000); + props.put("mongo.shell.path", (IS_WINDOWS ? "" : "sh ") + MONGO_SHELL); + props.put("mongo.shell.command.table.limit", "10000"); + props.put("mongo.server.database", "test"); + props.put("mongo.server.username", ""); + props.put("mongo.server.password", ""); + props.put("mongo.server.authenticationDatabase", ""); + props.put("mongo.shell.command.timeout", "10000"); + props.put("mongo.interpreter.concurrency.max", "10"); + props.put("mongo.server.host", "localhost"); + props.put("mongo.server.port", "27017"); + + interpreter.open(); + } + + @After + public void destroy(){ + interpreter.close(); + } + + @Test + public void testSuccess() { + final String userScript = "print('hello');"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + try { + out.flush(); + } catch (IOException ex) { + System.out.println(ex.getMessage()); + } + + final String resultScript = new String(getBufferBytes()); + + final String expectedScript = SHELL_EXTENSION.replace( + "TABLE_LIMIT_PLACEHOLDER", interpreter.getProperty("mongo.shell.command.table.limit")) + .replace("TARGET_DB_PLACEHOLDER", interpreter.getProperty("mongo.server.database")) + .replace("USER_NAME_PLACEHOLDER", interpreter.getProperty("mongo.server.username")) + .replace("PASSWORD_PLACEHOLDER", interpreter.getProperty("mongo.server.password")) + .replace("AUTH_DB_PLACEHOLDER", interpreter. getProperty("mongo.server.authenticationDatabase"))+ + userScript; + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", expectedScript, resultScript); + } + + @Test + public void testBadConf() { + props.setProperty("mongo.shell.path", "/bad/path/to/mongo"); + final InterpreterResult res = interpreter.interpret("print('hello')", context); + + assertSame(Code.ERROR, res.code()); + } + + @Override + public void onUpdateAll(InterpreterOutput interpreterOutput) { + + } + + @Override + public void onAppend(int i, InterpreterResultMessageOutput interpreterResultMessageOutput, + byte[] bytes) { + buffer.put(bytes); + } + + @Override + public void onUpdate(int i, InterpreterResultMessageOutput interpreterResultMessageOutput) { + } + + private byte[] getBufferBytes() { + buffer.flip(); + final byte[] bufferBytes = new byte[buffer.remaining()]; + buffer.get(bufferBytes); + return bufferBytes; + } +} diff --git a/pom.xml b/pom.xml index 58ecd09..de25b8b 100644 --- a/pom.xml +++ b/pom.xml @@ -96,6 +96,7 @@ <module>zeppelin-jupyter</module> <module>zeppelin-plugins</module> <module>zeppelin-distribution</module> + <module>mongodb</module> </modules> <properties> diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index dac35d4..90c212e 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -57,7 +57,8 @@ "src-noconflict/mode-sparql.js", "src-noconflict/keybinding-emacs.js", "src-noconflict/ext-language_tools.js", - "src-noconflict/theme-chrome.js" + "src-noconflict/theme-chrome.js", + "src-noconflict/mode-javascript.js" ], "version": "1.3.2", "name": "ace-builds"