ZEPPELIN-3196. Plugin framework for Zeppelin Engine ### What is this PR for? More details is in the design doc. https://docs.google.com/document/d/1QRgznfDKsi6XxR9FTJtUuiGAvgQvmc12FdIbHrmOZzc/edit?usp=sharing
### What type of PR is it? [ Improvement | Refactoring] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-3196 ### How should this be tested? * CI pass ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang <zjf...@apache.org> Closes #2760 from zjffdu/ZEPPELIN-3196 and squashes the following commits: 639c599 [Jeff Zhang] ZEPPELIN-3196. Plugin framework for Zeppelin Engine Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/3eea57ab Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/3eea57ab Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/3eea57ab Branch: refs/heads/master Commit: 3eea57ab26e8c4a8811bde64854b95f706858f10 Parents: db716c8 Author: Jeff Zhang <zjf...@apache.org> Authored: Wed Jan 31 19:46:44 2018 +0800 Committer: Jeff Zhang <zjf...@apache.org> Committed: Thu Mar 22 21:19:25 2018 +0800 ---------------------------------------------------------------------- .travis.yml | 23 +- pom.xml | 11 + .../src/assemble/distribution.xml | 3 + .../zeppelin/conf/ZeppelinConfiguration.java | 9 + zeppelin-plugins/notebookrepo/azure/pom.xml | 71 +++ .../notebook/repo/AzureNotebookRepo.java | 216 +++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../notebookrepo/filesystem/pom.xml | 236 ++++++++++ .../notebook/repo/FileSystemNotebookRepo.java | 103 +++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../repo/FileSystemNotebookRepoTest.java | 102 +++++ zeppelin-plugins/notebookrepo/gcs/pom.xml | 141 ++++++ .../zeppelin/notebook/repo/GCSNotebookRepo.java | 214 +++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../notebook/repo/GCSNotebookRepoTest.java | 235 ++++++++++ zeppelin-plugins/notebookrepo/git/pom.xml | 71 +++ .../zeppelin/notebook/repo/GitNotebookRepo.java | 205 +++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../notebook/repo/GitNotebookRepoTest.java | 385 ++++++++++++++++ .../NotebookRepoSyncInitializationTest.java | 161 +++++++ .../notebook/repo/NotebookRepoSyncTest.java | 444 +++++++++++++++++++ .../notebook/repo/mock/VFSNotebookRepoMock.java | 36 ++ .../git/src/test/resources/2A94M5J1Z/note.json | 341 ++++++++++++++ .../git/src/test/resources/2A94M5J2Z/note.json | 87 ++++ .../git/src/test/resources/log4j.properties | 50 +++ zeppelin-plugins/notebookrepo/github/pom.xml | 50 +++ .../notebook/repo/GitHubNotebookRepo.java | 126 ++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../notebook/repo/GitHubNotebookRepoTest.java | 207 +++++++++ .../src/test/resources/2A94M5J1Z/note.json | 341 ++++++++++++++ .../src/test/resources/2A94M5J2Z/note.json | 87 ++++ zeppelin-plugins/notebookrepo/mongodb/pom.xml | 55 +++ .../notebook/repo/MongoNotebookRepo.java | 222 ++++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + zeppelin-plugins/notebookrepo/s3/pom.xml | 51 +++ .../zeppelin/notebook/repo/S3NotebookRepo.java | 294 ++++++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + zeppelin-plugins/notebookrepo/vfs/pom.xml | 58 +++ .../zeppelin/notebook/repo/VFSNotebookRepo.java | 286 ++++++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../notebook/repo/TestVFSNotebookRepo.java | 113 +++++ .../notebookrepo/zeppelin-hub/pom.xml | 81 ++++ .../repo/zeppelinhub/ZeppelinHubRepo.java | 385 ++++++++++++++++ ...g.apache.zeppelin.notebook.repo.NotebookRepo | 18 + .../repo/zeppelinhub/ZeppelinHubRepoTest.java | 155 +++++++ .../websocket/ZeppelinClientTest.java | 127 ++++++ .../websocket/ZeppelinhubClientTest.java | 72 +++ .../websocket/mock/MockEchoWebsocketServer.java | 46 ++ .../websocket/mock/MockEventServlet.java | 14 + .../websocket/mock/MockEventSocket.java | 38 ++ .../protocol/ZeppelinhubMessageTest.java | 43 ++ zeppelin-plugins/pom.xml | 124 ++++++ zeppelin-server/pom.xml | 36 -- .../org/apache/zeppelin/realm/PamRealm.java | 2 +- .../apache/zeppelin/realm/ZeppelinHubRealm.java | 26 +- .../apache/zeppelin/socket/NotebookServer.java | 5 +- .../src/test/resources/log4j.properties | 3 +- zeppelin-zengine/pom.xml | 298 +------------ .../recovery/FileSystemRecoveryStorage.java | 11 - .../java/org/apache/zeppelin/notebook/Note.java | 5 + .../org/apache/zeppelin/notebook/Notebook.java | 2 +- .../notebook/repo/AzureNotebookRepo.java | 209 --------- .../notebook/repo/FileSystemNotebookRepo.java | 99 ----- .../zeppelin/notebook/repo/GCSNotebookRepo.java | 208 --------- .../notebook/repo/GitHubNotebookRepo.java | 126 ------ .../zeppelin/notebook/repo/GitNotebookRepo.java | 190 -------- .../notebook/repo/MongoNotebookRepo.java | 217 --------- .../zeppelin/notebook/repo/NotebookRepo.java | 4 + .../notebook/repo/NotebookRepoSync.java | 45 +- .../zeppelin/notebook/repo/S3NotebookRepo.java | 290 ------------ .../zeppelin/notebook/repo/VFSNotebookRepo.java | 280 ------------ .../repo/zeppelinhub/ZeppelinHubRepo.java | 376 ---------------- .../websocket/ZeppelinhubClient.java | 4 +- .../apache/zeppelin/plugin/PluginManager.java | 95 ++++ .../helium/HeliumApplicationFactoryTest.java | 7 +- .../apache/zeppelin/notebook/NotebookTest.java | 324 ++++++++++---- .../repo/FileSystemNotebookRepoTest.java | 101 ----- .../notebook/repo/GCSNotebookRepoTest.java | 235 ---------- .../notebook/repo/GitHubNotebookRepoTest.java | 207 --------- .../notebook/repo/GitNotebookRepoTest.java | 385 ---------------- .../NotebookRepoSyncInitializationTest.java | 159 ------- .../notebook/repo/NotebookRepoSyncTest.java | 437 ------------------ .../notebook/repo/VFSNotebookRepoTest.java | 167 ------- .../notebook/repo/mock/VFSNotebookRepoMock.java | 42 -- .../repo/zeppelinhub/ZeppelinHubRepoTest.java | 154 ------- .../websocket/ZeppelinClientTest.java | 127 ------ .../websocket/ZeppelinhubClientTest.java | 72 --- .../websocket/mock/MockEchoWebsocketServer.java | 46 -- .../websocket/mock/MockEventServlet.java | 14 - .../websocket/mock/MockEventSocket.java | 38 -- .../protocol/ZeppelinhubMessageTest.java | 43 -- .../src/test/resources/2A94M5J1Z/note.json | 341 -------------- .../src/test/resources/2A94M5J2Z/note.json | 87 ---- .../src/test/resources/log4j.properties | 3 +- 94 files changed, 6651 insertions(+), 5150 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/.travis.yml ---------------------------------------------------------------------- diff --git a/.travis.yml b/.travis.yml index e113ae3..5a81441 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ matrix: # Test License compliance using RAT tool - jdk: "openjdk7" dist: trusty - env: SCALA_VER="2.11" PROFILE="-Prat" BUILD_FLAG="clean" TEST_FLAG="org.apache.rat:apache-rat-plugin:check" TEST_PROJECTS="" + env: BUILD_PLUGINS="false" SCALA_VER="2.11" PROFILE="-Prat" BUILD_FLAG="clean" TEST_FLAG="org.apache.rat:apache-rat-plugin:check" TEST_PROJECTS="" # Run e2e tests (in zeppelin-web) # chrome dropped the support for precise (ubuntu 12.04), so need to use trusty @@ -53,7 +53,7 @@ matrix: sudo: false dist: trusty jdk: "oraclejdk8" - env: CI="true" WEB_E2E="true" PYTHON="2" SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Phadoop2 -Pscala-2.11" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_MODULES="-pl zeppelin-web" TEST_PROJECTS="-Pweb-e2e" + env: BUILD_PLUGINS="false" CI="true" WEB_E2E="true" PYTHON="2" SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Phadoop2 -Pscala-2.11" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_MODULES="-pl zeppelin-web" TEST_PROJECTS="-Pweb-e2e" addons: apt: packages: @@ -66,19 +66,19 @@ matrix: - sudo: required jdk: "oraclejdk8" dist: trusty - env: PYTHON="3" SPARKR="true" PROFILE="-Pspark-2.2 -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_PROJECTS="-Dtests.to.exclude=**/SparkIntegrationTest.java,**/ZeppelinSparkClusterTest.java,**/org/apache/zeppelin/spark/*,**/HeliumApplicationFactoryTest.java -DfailIfNoTests=false" + env: BUILD_PLUGINS="true" PYTHON="3" SPARKR="true" PROFILE="-Pspark-2.2 -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="install -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_PROJECTS="-Dtests.to.exclude=**/SparkIntegrationTest.java,**/ZeppelinSparkClusterTest.java,**/org/apache/zeppelin/spark/*,**/HeliumApplicationFactoryTest.java -DfailIfNoTests=false" # Test selenium with spark module for 1.6.3 - jdk: "oraclejdk8" dist: trusty addons: firefox: "31.0" - env: CI="true" PYTHON="2" SCALA_VER="2.10" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pspark-1.6 -Phadoop2 -Phadoop-2.6 -Phelium-dev -Pexamples -Pintegration" BUILD_FLAG="install -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl .,zeppelin-integration -DfailIfNoTests=false" + env: BUILD_PLUGINS="true" CI="true" PYTHON="2" SCALA_VER="2.10" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pspark-1.6 -Phadoop2 -Phadoop-2.6 -Phelium-dev -Pexamples -Pintegration" BUILD_FLAG="install -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl .,zeppelin-integration -DfailIfNoTests=false" # Test interpreter modules - jdk: "openjdk7" dist: trusty - env: PYTHON="3" SCALA_VER="2.10" PROFILE="-Pscalding" BUILD_FLAG="package -DskipTests -DskipRat -Pr" TEST_FLAG="test -DskipRat" MODULES="-pl $(echo .,zeppelin-interpreter,${INTERPRETERS} | sed 's/!//g')" TEST_PROJECTS="" + env: BUILD_PLUGINS="false" PYTHON="3" SCALA_VER="2.10" PROFILE="-Pscalding" BUILD_FLAG="package -DskipTests -DskipRat -Pr" TEST_FLAG="test -DskipRat" MODULES="-pl $(echo .,zeppelin-interpreter,${INTERPRETERS} | sed 's/!//g')" TEST_PROJECTS="" # Run ZeppelinSparkClusterTest & SparkIntegrationTest in one build would exceed the time limitation of travis, so running them separately @@ -86,35 +86,35 @@ matrix: - sudo: required jdk: "oraclejdk8" dist: trusty - env: PYTHON="2" PROFILE="-Pspark-2.2" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl zeppelin-zengine,zeppelin-server,spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.* -DfailIfNoTests=false" + env: BUILD_PLUGINS="true" PYTHON="2" PROFILE="-Pspark-2.2" SPARKR="true" BUILD_FLAG="install -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl zeppelin-zengine,zeppelin-server,spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.* -DfailIfNoTests=false" # Integration test of spark interpreter with different spark versions under python3, only run SparkIntegrationTest. Also run spark unit test of spark 1.6 in this build. - sudo: required jdk: "oraclejdk8" dist: trusty - env: PYTHON="3" PROFILE="-Pspark-1.6" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl zeppelin-zengine,spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=SparkIntegrationTest,org.apache.zeppelin.spark.* -DfailIfNoTests=false" + env: BUILD_PLUGINS="true" PYTHON="3" PROFILE="-Pspark-1.6" SPARKR="true" BUILD_FLAG="install -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl zeppelin-zengine,spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=SparkIntegrationTest,org.apache.zeppelin.spark.* -DfailIfNoTests=false" # Test spark module for 2.1.0 with scala 2.11 - jdk: "openjdk7" dist: trusty - env: PYTHON="2" SCALA_VER="2.11" PROFILE="-Pspark-2.1 -Phadoop2 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=org.apache.zeppelin.spark.* -DfailIfNoTests=false" + env: BUILD_PLUGINS="false" PYTHON="2" SCALA_VER="2.11" PROFILE="-Pspark-2.1 -Phadoop2 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=org.apache.zeppelin.spark.* -DfailIfNoTests=false" # Test spark module for 2.0.2 with scala 2.11 - jdk: "oraclejdk8" dist: trusty - env: PYTHON="2" SCALA_VER="2.11" PROFILE="-Pspark-2.0 -Phadoop3 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=org.apache.zeppelin.spark.* -DfailIfNoTests=false" + env: BUILD_PLUGINS="false" PYTHON="2" SCALA_VER="2.11" PROFILE="-Pspark-2.0 -Phadoop3 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat -am" TEST_FLAG="test -DskipRat -am" MODULES="-pl spark/interpreter,spark/spark-dependencies" TEST_PROJECTS="-Dtest=org.apache.zeppelin.spark.* -DfailIfNoTests=false" # Test python/pyspark with python 2, livy 0.5 - sudo: required dist: trusty jdk: "openjdk7" - env: PYTHON="2" SPARK_VER="1.6.3" HADOOP_VER="2.6" LIVY_VER="0.5.0-incubating" PROFILE="" BUILD_FLAG="install -am -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl livy" TEST_PROJECTS="" + env: BUILD_PLUGINS="false" PYTHON="2" SPARK_VER="1.6.3" HADOOP_VER="2.6" LIVY_VER="0.5.0-incubating" PROFILE="" BUILD_FLAG="install -am -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl livy" TEST_PROJECTS="" # Test livy 0.5 with spark 2.2.0 under python3 - sudo: required dist: trusty jdk: "openjdk8" - env: PYTHON="3" SPARK_VER="2.2.0" HADOOP_VER="2.6" LIVY_VER="0.5.0-incubating" PROFILE="" BUILD_FLAG="install -am -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl livy" TEST_PROJECTS="" + env: BUILD_PLUGINS="false" PYTHON="3" SPARK_VER="2.2.0" HADOOP_VER="2.6" LIVY_VER="0.5.0-incubating" PROFILE="" BUILD_FLAG="install -am -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl livy" TEST_PROJECTS="" before_install: # check files included in commit range, clear bower_components if a bower.json file has changed. @@ -136,6 +136,7 @@ before_install: install: - echo "mvn $BUILD_FLAG $MODULES $PROFILE -B" - mvn $BUILD_FLAG $MODULES $PROFILE -B + - if [ $BUILD_PLUGINS == "true" ]; then echo "mvn clean package -pl zeppelin-plugins -amd -B"; mvn clean package -pl zeppelin-plugins -amd -B; fi before_script: - if [[ -n $SPARK_VER ]]; then travis_retry ./testing/downloadSpark.sh $SPARK_VER $HADOOP_VER; fi http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index ca6c409..a2b5553 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,7 @@ <module>zeppelin-web</module> <module>zeppelin-server</module> <module>zeppelin-jupyter</module> + <module>zeppelin-plugins</module> <module>zeppelin-distribution</module> </modules> @@ -115,6 +116,7 @@ <commons.logging.version>1.1.1</commons.logging.version> <commons.cli.version>1.3.1</commons.cli.version> <shiro.version>1.3.2</shiro.version> + <joda.version>2.9.9</joda.version> <!-- test library versions --> <junit.version>4.12</junit.version> @@ -247,6 +249,12 @@ <version>${commons.cli.version}</version> </dependency> + <dependency> + <groupId>joda-time</groupId> + <artifactId>joda-time</artifactId> + <version>${joda.version}</version> + </dependency> + <!-- Apache Shiro --> <dependency> <groupId>org.apache.shiro</groupId> @@ -592,6 +600,9 @@ <version>${plugin.surefire.version}</version> <configuration combine.children="append"> <argLine>-Xmx2g -Xms1g -Dfile.encoding=UTF-8</argLine> + <environmentVariables> + <IS_ZEPPELIN_TEST>true</IS_ZEPPELIN_TEST> + </environmentVariables> <excludes> <exclude>${tests.to.exclude}</exclude> </excludes> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-distribution/src/assemble/distribution.xml ---------------------------------------------------------------------- diff --git a/zeppelin-distribution/src/assemble/distribution.xml b/zeppelin-distribution/src/assemble/distribution.xml index 5c369e2..068f85b 100644 --- a/zeppelin-distribution/src/assemble/distribution.xml +++ b/zeppelin-distribution/src/assemble/distribution.xml @@ -92,6 +92,9 @@ <directory>../notebook</directory> </fileSet> <fileSet> + <directory>../plugins</directory> + </fileSet> + <fileSet> <outputDirectory>/lib/interpreter</outputDirectory> <directory>../zeppelin-interpreter/target/lib</directory> </fileSet> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java ---------------------------------------------------------------------- diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index db6e150..2960cd0 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -347,10 +347,18 @@ public class ZeppelinConfiguration extends XMLConfiguration { return getString(ConfVars.ZEPPELIN_NOTEBOOK_DIR); } + public String getPluginsDir() { + return getRelativeDir(getString(ConfVars.ZEPPELIN_PLUGINS_DIR)); + } + public String getRecoveryDir() { return getRelativeDir(ConfVars.ZEPPELIN_RECOVERY_DIR); } + public String getNotebookStorageClass() { + return getString(ConfVars.ZEPPELIN_NOTEBOOK_STORAGE); + } + public String getRecoveryStorageClass() { return getString(ConfVars.ZEPPELIN_RECOVERY_STORAGE_CLASS); } @@ -711,6 +719,7 @@ public class ZeppelinConfiguration extends XMLConfiguration { ZEPPELIN_RECOVERY_DIR("zeppelin.recovery.dir", "recovery"), ZEPPELIN_RECOVERY_STORAGE_CLASS("zeppelin.recovery.storage.class", "org.apache.zeppelin.interpreter.recovery.NullRecoveryStorage"), + ZEPPELIN_PLUGINS_DIR("zeppelin.plugins.dir", "plugins"), // use specified notebook (id) as homescreen ZEPPELIN_NOTEBOOK_HOMESCREEN("zeppelin.notebook.homescreen", null), http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/azure/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/azure/pom.xml b/zeppelin-plugins/notebookrepo/azure/pom.xml new file mode 100644 index 0000000..f663de6 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/azure/pom.xml @@ -0,0 +1,71 @@ +<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>zengine-plugins-parent</artifactId> + <groupId>org.apache.zeppelin</groupId> + <version>0.9.0-SNAPSHOT</version> + <relativePath>../../../zeppelin-plugins</relativePath> + </parent> + + <groupId>org.apache.zeppelin</groupId> + <artifactId>notebookrepo-azure</artifactId> + <packaging>jar</packaging> + <version>0.9.0-SNAPSHOT</version> + <name>Zeppelin: Plugin AzureNotebookRepo</name> + <description>NotebookRepo implementation based on Azure</description> + + <properties> + <adl.sdk.version>2.1.4</adl.sdk.version> + <azure.storage.version>4.0.0</azure.storage.version> + <plugin.name>NotebookRepo/AzureNotebookRepo</plugin.name> + </properties> + + <dependencies> + <dependency> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-data-lake-store-sdk</artifactId> + <version>${adl.sdk.version}</version> + </dependency> + <dependency> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-storage</artifactId> + <version>${azure.storage.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/azure/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/azure/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java b/zeppelin-plugins/notebookrepo/azure/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java new file mode 100644 index 0000000..12dbd90 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/azure/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java @@ -0,0 +1,216 @@ +/* + * 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.notebook.repo; + +import com.microsoft.azure.storage.CloudStorageAccount; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.file.CloudFile; +import com.microsoft.azure.storage.file.CloudFileClient; +import com.microsoft.azure.storage.file.CloudFileDirectory; +import com.microsoft.azure.storage.file.CloudFileShare; +import com.microsoft.azure.storage.file.ListFileItem; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteInfo; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Azure storage backend for notebooks + */ +public class AzureNotebookRepo implements NotebookRepo { + private static final Logger LOG = LoggerFactory.getLogger(AzureNotebookRepo.class); + + private ZeppelinConfiguration conf; + private String user; + private String shareName; + private CloudFileDirectory rootDir; + + public AzureNotebookRepo() { + + } + + public void init(ZeppelinConfiguration conf) throws IOException { + this.conf = conf; + user = conf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_AZURE_USER); + shareName = conf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_AZURE_SHARE); + + try { + CloudStorageAccount account = CloudStorageAccount.parse( + conf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_AZURE_CONNECTION_STRING)); + CloudFileClient client = account.createCloudFileClient(); + CloudFileShare share = client.getShareReference(shareName); + share.createIfNotExists(); + + CloudFileDirectory userDir = StringUtils.isBlank(user) ? + share.getRootDirectoryReference() : + share.getRootDirectoryReference().getDirectoryReference(user); + userDir.createIfNotExists(); + + rootDir = userDir.getDirectoryReference("notebook"); + rootDir.createIfNotExists(); + } catch (Exception e) { + throw new IOException(e); + } + } + + @Override + public List<NoteInfo> list(AuthenticationInfo subject) throws IOException { + List<NoteInfo> infos = new LinkedList<>(); + NoteInfo info = null; + + for (ListFileItem item : rootDir.listFilesAndDirectories()) { + if (item.getClass() == CloudFileDirectory.class) { + CloudFileDirectory dir = (CloudFileDirectory) item; + + try { + if (dir.getFileReference("note.json").exists()) { + info = new NoteInfo(getNote(dir.getName())); + + if (info != null) { + infos.add(info); + } + } + } catch (StorageException | URISyntaxException e) { + String msg = "Error enumerating notebooks from Azure storage"; + LOG.error(msg, e); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + } + } + + return infos; + } + + private Note getNote(String noteId) throws IOException { + InputStream ins = null; + + try { + CloudFileDirectory dir = rootDir.getDirectoryReference(noteId); + CloudFile file = dir.getFileReference("note.json"); + + ins = file.openRead(); + } catch (URISyntaxException | StorageException e) { + String msg = String.format("Error reading notebook %s from Azure storage", noteId); + + LOG.error(msg, e); + + throw new IOException(msg, e); + } + + String json = IOUtils.toString(ins, + conf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_ENCODING)); + ins.close(); + return Note.fromJson(json); + } + + @Override + public Note get(String noteId, AuthenticationInfo subject) throws IOException { + return getNote(noteId); + } + + @Override + public void save(Note note, AuthenticationInfo subject) throws IOException { + String json = note.toJson(); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + Writer writer = new OutputStreamWriter(output); + writer.write(json); + writer.close(); + output.close(); + + byte[] buffer = output.toByteArray(); + + try { + CloudFileDirectory dir = rootDir.getDirectoryReference(note.getId()); + dir.createIfNotExists(); + + CloudFile cloudFile = dir.getFileReference("note.json"); + cloudFile.uploadFromByteArray(buffer, 0, buffer.length); + } catch (URISyntaxException | StorageException e) { + String msg = String.format("Error saving notebook %s to Azure storage", note.getId()); + + LOG.error(msg, e); + + throw new IOException(msg, e); + } + } + + // unfortunately, we need to use a recursive delete here + private void delete(ListFileItem item) throws StorageException { + if (item.getClass() == CloudFileDirectory.class) { + CloudFileDirectory dir = (CloudFileDirectory) item; + + for (ListFileItem subItem : dir.listFilesAndDirectories()) { + delete(subItem); + } + + dir.deleteIfExists(); + } else if (item.getClass() == CloudFile.class) { + CloudFile file = (CloudFile) item; + + file.deleteIfExists(); + } + } + + @Override + public void remove(String noteId, AuthenticationInfo subject) throws IOException { + try { + CloudFileDirectory dir = rootDir.getDirectoryReference(noteId); + + delete(dir); + } catch (URISyntaxException | StorageException e) { + String msg = String.format("Error deleting notebook %s from Azure storage", noteId); + + LOG.error(msg, e); + + throw new IOException(msg, e); + } + } + + @Override + public void close() { + } + + @Override + public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + return Collections.emptyList(); + } + + @Override + public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + } + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/azure/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/azure/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo b/zeppelin-plugins/notebookrepo/azure/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo new file mode 100644 index 0000000..b028d54 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/azure/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo @@ -0,0 +1,18 @@ +# +# 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. +# + +org.apache.zeppelin.notebook.repo.AzureNotebookRepo \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/filesystem/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/filesystem/pom.xml b/zeppelin-plugins/notebookrepo/filesystem/pom.xml new file mode 100644 index 0000000..422b7b6 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/filesystem/pom.xml @@ -0,0 +1,236 @@ +<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>zengine-plugins-parent</artifactId> + <groupId>org.apache.zeppelin</groupId> + <version>0.9.0-SNAPSHOT</version> + <relativePath>../../../zeppelin-plugins</relativePath> + </parent> + + <groupId>org.apache.zeppelin</groupId> + <artifactId>notebookrepo-filesystem</artifactId> + <packaging>jar</packaging> + <version>0.9.0-SNAPSHOT</version> + <name>Zeppelin: Plugin FileSystemNotebookRepo</name> + <description>NotebookRepo implementation based on Hadoop FileSystem</description> + + <properties> + <adl.sdk.version>2.1.4</adl.sdk.version> + <plugin.name>NotebookRepo/FileSystemNotebookRepo</plugin.name> + </properties> + + <profiles> + + <profile> + <id>hadoop2-azure</id> + <properties> + <hadoop.version>2.7.3</hadoop.version> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-azure</artifactId> + <version>${hadoop.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </exclusion> + <exclusion> + <groupId>com.jcraf</groupId> + <artifactId>jsch</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-data-lake-store-sdk</artifactId> + <version>${adl.sdk.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + </profile> + + <profile> + <id>hadoop2-aws</id> + <properties> + <hadoop.version>2.7.3</hadoop.version> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-aws</artifactId> + <version>${hadoop.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </exclusion> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </exclusion> + <exclusion> + <groupId>joda-time</groupId> + <artifactId>joda-time</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + </profile> + + <profile> + <id>hadoop3-azure</id> + <properties> + <hadoop.version>3.0.0</hadoop.version> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-azure</artifactId> + <version>${hadoop.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>com.jcraft</groupId> + <artifactId>jsch</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + </exclusion> + <exclusion> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-mapper-asl</artifactId> + </exclusion> + <exclusion> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.zookeeper</groupId> + <artifactId>zookeeper</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + </exclusion> + <exclusion> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-core-asl</artifactId> + </exclusion> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-util</artifactId> + </exclusion> + <exclusion> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-core</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-azure-datalake</artifactId> + <version>${hadoop.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + </profile> + + <profile> + <id>hadoop3-aws</id> + <properties> + <hadoop.version>3.0.0</hadoop.version> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-aws</artifactId> + <version>${hadoop.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </exclusion> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </exclusion> + <exclusion> + <groupId>joda-time</groupId> + <artifactId>joda-time</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + </profile> + </profiles> +</project> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/filesystem/src/main/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepo.java ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/filesystem/src/main/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepo.java b/zeppelin-plugins/notebookrepo/filesystem/src/main/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepo.java new file mode 100644 index 0000000..5d9c85c --- /dev/null +++ b/zeppelin-plugins/notebookrepo/filesystem/src/main/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepo.java @@ -0,0 +1,103 @@ +package org.apache.zeppelin.notebook.repo; + +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.IOUtils; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.apache.zeppelin.notebook.FileSystemStorage; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteInfo; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * NotebookRepos for hdfs. + * + * Assume the notebook directory structure is as following + * - notebookdir + * - noteId/note.json + * - noteId/note.json + * - noteId/note.json + */ +public class FileSystemNotebookRepo implements NotebookRepo { + private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemNotebookRepo.class); + + private FileSystemStorage fs; + private Path notebookDir; + + public FileSystemNotebookRepo() { + + } + + public void init(ZeppelinConfiguration zConf) throws IOException { + this.fs = new FileSystemStorage(zConf, zConf.getNotebookDir()); + LOGGER.info("Creating FileSystem: " + this.fs.getFs().getClass().getName() + + " for Zeppelin Notebook."); + this.notebookDir = this.fs.makeQualified(new Path(zConf.getNotebookDir())); + LOGGER.info("Using folder {} to store notebook", notebookDir); + this.fs.tryMkDir(notebookDir); + } + + @Override + public List<NoteInfo> list(AuthenticationInfo subject) throws IOException { + List<Path> notePaths = fs.list(new Path(notebookDir, "*/note.json")); + List<NoteInfo> noteInfos = new ArrayList<>(); + for (Path path : notePaths) { + NoteInfo noteInfo = new NoteInfo(path.getParent().getName(), "", null); + noteInfos.add(noteInfo); + } + return noteInfos; + } + + @Override + public Note get(final String noteId, AuthenticationInfo subject) throws IOException { + String content = this.fs.readFile( + new Path(notebookDir.toString() + "/" + noteId + "/note.json")); + return Note.fromJson(content); + } + + @Override + public void save(final Note note, AuthenticationInfo subject) throws IOException { + this.fs.writeFile(note.toJson(), + new Path(notebookDir.toString() + "/" + note.getId() + "/note.json"), + true); + } + + @Override + public void remove(final String noteId, AuthenticationInfo subject) throws IOException { + this.fs.delete(new Path(notebookDir.toString() + "/" + noteId)); + } + + @Override + public void close() { + LOGGER.warn("close is not implemented for HdfsNotebookRepo"); + } + + @Override + public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) { + LOGGER.warn("getSettings is not implemented for HdfsNotebookRepo"); + return null; + } + + @Override + public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) { + LOGGER.warn("updateSettings is not implemented for HdfsNotebookRepo"); + } + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/filesystem/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/filesystem/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo b/zeppelin-plugins/notebookrepo/filesystem/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo new file mode 100644 index 0000000..06ef085 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/filesystem/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo @@ -0,0 +1,18 @@ +# +# 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. +# + +org.apache.zeppelin.notebook.repo.FileSystemNotebookRepo \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/filesystem/src/test/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepoTest.java ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/filesystem/src/test/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepoTest.java b/zeppelin-plugins/notebookrepo/filesystem/src/test/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepoTest.java new file mode 100644 index 0000000..5fd8becc --- /dev/null +++ b/zeppelin-plugins/notebookrepo/filesystem/src/test/java/org/apache/zeppelin/notebook/repo/FileSystemNotebookRepoTest.java @@ -0,0 +1,102 @@ +package org.apache.zeppelin.notebook.repo; + + +import org.apache.commons.io.FileUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class FileSystemNotebookRepoTest { + + private ZeppelinConfiguration zConf; + private Configuration hadoopConf; + private FileSystem fs; + private FileSystemNotebookRepo hdfsNotebookRepo; + private String notebookDir; + private AuthenticationInfo authInfo = AuthenticationInfo.ANONYMOUS; + + @Before + public void setUp() throws IOException { + notebookDir = Files.createTempDirectory("FileSystemNotebookRepoTest").toFile().getAbsolutePath(); + zConf = new ZeppelinConfiguration(); + System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(), notebookDir); + hadoopConf = new Configuration(); + fs = FileSystem.get(hadoopConf); + hdfsNotebookRepo = new FileSystemNotebookRepo(); + hdfsNotebookRepo.init(zConf); + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(new File(notebookDir)); + } + + @Test + public void testBasics() throws IOException { + assertEquals(0, hdfsNotebookRepo.list(authInfo).size()); + + // create a new note + Note note = new Note(); + note.setName("title_1"); + + Map<String, Object> config = new HashMap<>(); + config.put("config_1", "value_1"); + note.setConfig(config); + hdfsNotebookRepo.save(note, authInfo); + assertEquals(1, hdfsNotebookRepo.list(authInfo).size()); + + // read this note from hdfs + Note note_copy = hdfsNotebookRepo.get(note.getId(), authInfo); + assertEquals(note.getName(), note_copy.getName()); + assertEquals(note.getConfig(), note_copy.getConfig()); + + // update this note + note.setName("title_2"); + hdfsNotebookRepo.save(note, authInfo); + assertEquals(1, hdfsNotebookRepo.list(authInfo).size()); + note_copy = hdfsNotebookRepo.get(note.getId(), authInfo); + assertEquals(note.getName(), note_copy.getName()); + assertEquals(note.getConfig(), note_copy.getConfig()); + + // delete this note + hdfsNotebookRepo.remove(note.getId(), authInfo); + assertEquals(0, hdfsNotebookRepo.list(authInfo).size()); + } + + @Test + public void testComplicatedScenarios() throws IOException { + // scenario_1: notebook_dir is not clean. There're some unrecognized dir and file under notebook_dir + fs.mkdirs(new Path(notebookDir, "1/2")); + OutputStream out = fs.create(new Path(notebookDir, "1/a.json")); + out.close(); + + assertEquals(0, hdfsNotebookRepo.list(authInfo).size()); + + // scenario_2: note_folder is existed. + // create a new note + Note note = new Note(); + note.setName("title_1"); + Map<String, Object> config = new HashMap<>(); + config.put("config_1", "value_1"); + note.setConfig(config); + + fs.mkdirs(new Path(notebookDir, note.getId())); + hdfsNotebookRepo.save(note, authInfo); + assertEquals(1, hdfsNotebookRepo.list(authInfo).size()); + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/gcs/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/gcs/pom.xml b/zeppelin-plugins/notebookrepo/gcs/pom.xml new file mode 100644 index 0000000..15aa7c7 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/gcs/pom.xml @@ -0,0 +1,141 @@ +<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>zengine-plugins-parent</artifactId> + <groupId>org.apache.zeppelin</groupId> + <version>0.9.0-SNAPSHOT</version> + <relativePath>../../../zeppelin-plugins</relativePath> + </parent> + + <groupId>org.apache.zeppelin</groupId> + <artifactId>notebookrepo-gcs</artifactId> + <packaging>jar</packaging> + <version>0.9.0-SNAPSHOT</version> + <name>Zeppelin: Plugin GCSNotebookRepo</name> + <description>NotebookRepo implementation based on Google Cloud Storage</description> + + <properties> + <gcs.storage.version>1.14.0</gcs.storage.version> + <google.testing.nio.version>0.32.0-alpha</google.testing.nio.version> + <google.truth.version>0.27</google.truth.version> + <plugin.name>NotebookRepo/GCSNotebookRepo</plugin.name> + </properties> + + <dependencies> + <dependency> + <groupId>com.google.cloud</groupId> + <artifactId>google-cloud-storage</artifactId> + <version>${gcs.storage.version}</version> + <exclusions> + <exclusion> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.api</groupId> + <artifactId>api-common</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.http-client</groupId> + <artifactId>google-http-client-jackson2</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.http-client</groupId> + <artifactId>google-http-client</artifactId> + </exclusion> + <exclusion> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-core-asl</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>com.google.cloud</groupId> + <artifactId>google-cloud-nio</artifactId> + <version>${google.testing.nio.version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>com.google.truth</groupId> + <artifactId>truth</artifactId> + <version>${google.truth.version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>com.google.api</groupId> + <artifactId>api-common</artifactId> + <version>1.2.0</version> + <exclusions> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>com.google.http-client</groupId> + <artifactId>google-http-client-jackson2</artifactId> + <version>1.23.0</version> + <exclusions> + <exclusion> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/gcs/src/main/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepo.java ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/gcs/src/main/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepo.java b/zeppelin-plugins/notebookrepo/gcs/src/main/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepo.java new file mode 100644 index 0000000..ad99aba --- /dev/null +++ b/zeppelin-plugins/notebookrepo/gcs/src/main/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepo.java @@ -0,0 +1,214 @@ +/* + * 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.notebook.repo; + +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobListOption; +import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.StorageOptions; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.gson.JsonParseException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteInfo; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A NotebookRepo implementation for storing notebooks in Google Cloud Storage. + * + * Notes are stored in the GCS "directory" specified by zeppelin.notebook.gcs.dir. This path + * must be in the form gs://bucketName/path/to/Dir. The bucket must already exist. N.B: GCS is an + * object store, so this "directory" should not itself be an object. Instead, it represents the base + * path for the note.json files. + * + * Authentication is provided by google-auth-library-java. + * @see <a href="https://github.com/google/google-auth-library-java"> + * google-auth-library-java</a>. + */ +public class GCSNotebookRepo implements NotebookRepo { + + private static final Logger LOG = LoggerFactory.getLogger(GCSNotebookRepo.class); + private String encoding; + private String bucketName; + private Optional<String> basePath; + private Pattern noteNamePattern; + private Storage storage; + + public GCSNotebookRepo() { + } + + @VisibleForTesting + public GCSNotebookRepo(ZeppelinConfiguration zConf, Storage storage) throws IOException { + init(zConf); + this.storage = storage; + } + + @Override + public void init(ZeppelinConfiguration zConf) throws IOException { + this.encoding = zConf.getString(ConfVars.ZEPPELIN_ENCODING); + + String gcsStorageDir = zConf.getGCSStorageDir(); + if (gcsStorageDir.isEmpty()) { + throw new IOException("GCS storage directory must be set using 'zeppelin.notebook.gcs.dir'"); + } + if (!gcsStorageDir.startsWith("gs://")) { + throw new IOException(String.format( + "GCS storage directory '%s' must start with 'gs://'.", gcsStorageDir)); + } + String storageDirWithoutScheme = gcsStorageDir.substring("gs://".length()); + + // pathComponents excludes empty string if trailing slash is present + List<String> pathComponents = Arrays.asList(storageDirWithoutScheme.split("/")); + if (pathComponents.size() < 1) { + throw new IOException(String.format( + "GCS storage directory '%s' must be in the form gs://bucketname/path/to/dir", + gcsStorageDir)); + } + this.bucketName = pathComponents.get(0); + if (pathComponents.size() > 1) { + this.basePath = Optional.of(StringUtils.join( + pathComponents.subList(1, pathComponents.size()), "/")); + } else { + this.basePath = Optional.absent(); + } + + // Notes are stored at gs://bucketName/basePath/<note-id>/note.json + if (basePath.isPresent()) { + this.noteNamePattern = Pattern.compile( + "^" + Pattern.quote(basePath.get() + "/") + "([^/]+)/note\\.json$"); + } else { + this.noteNamePattern = Pattern.compile("^([^/]+)/note\\.json$"); + } + + this.storage = StorageOptions.getDefaultInstance().getService(); + } + + private BlobId makeBlobId(String noteId) { + if (basePath.isPresent()) { + return BlobId.of(bucketName, basePath.get() + "/" + noteId + "/note.json"); + } else { + return BlobId.of(bucketName, noteId + "/note.json"); + } + } + + @Override + public List<NoteInfo> list(AuthenticationInfo subject) throws IOException { + try { + List<NoteInfo> infos = new ArrayList<>(); + Iterable<Blob> blobsUnderDir; + if (basePath.isPresent()) { + blobsUnderDir = storage + .list(bucketName, BlobListOption.prefix(this.basePath.get() + "/")) + .iterateAll(); + } else { + blobsUnderDir = storage + .list(bucketName) + .iterateAll(); + } + for (Blob b : blobsUnderDir) { + Matcher matcher = noteNamePattern.matcher(b.getName()); + if (matcher.matches()) { + // Callers only use the id field, so do not fetch each note + // This matches the implementation in FileSystemNoteRepo#list + infos.add(new NoteInfo(matcher.group(1), "", null)); + } + } + return infos; + } catch (StorageException se) { + throw new IOException("Could not list GCS directory: " + se.getMessage(), se); + } + } + + @Override + public Note get(String noteId, AuthenticationInfo subject) throws IOException { + BlobId blobId = makeBlobId(noteId); + byte[] contents; + try { + contents = storage.readAllBytes(blobId); + } catch (StorageException se) { + throw new IOException("Could not read " + blobId.toString() + ": " + se.getMessage(), se); + } + + try { + return Note.fromJson(new String(contents, encoding)); + } catch (JsonParseException jpe) { + throw new IOException( + "Could note parse as json " + blobId.toString() + jpe.getMessage(), jpe); + } + } + + @Override + public void save(Note note, AuthenticationInfo subject) throws IOException { + BlobInfo info = BlobInfo.newBuilder(makeBlobId(note.getId())) + .setContentType("application/json") + .build(); + try { + storage.create(info, note.toJson().getBytes("UTF-8")); + } catch (StorageException se) { + throw new IOException("Could not write " + info.toString() + ": " + se.getMessage(), se); + } + } + + @Override + public void remove(String noteId, AuthenticationInfo subject) throws IOException { + Preconditions.checkArgument(!Strings.isNullOrEmpty(noteId)); + BlobId blobId = makeBlobId(noteId); + try { + boolean deleted = storage.delete(blobId); + if (!deleted) { + throw new IOException("Tried to remove nonexistent blob " + blobId.toString()); + } + } catch (StorageException se) { + throw new IOException("Could not remove " + blobId.toString() + ": " + se.getMessage(), se); + } + } + + @Override + public void close() { + //no-op + } + + @Override + public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) { + LOG.warn("getSettings is not implemented for GCSNotebookRepo"); + return Collections.emptyList(); + } + + @Override + public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) { + LOG.warn("updateSettings is not implemented for GCSNotebookRepo"); + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/gcs/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/gcs/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo b/zeppelin-plugins/notebookrepo/gcs/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo new file mode 100644 index 0000000..784027b --- /dev/null +++ b/zeppelin-plugins/notebookrepo/gcs/src/main/resources/META-INF/services/org.apache.zeppelin.notebook.repo.NotebookRepo @@ -0,0 +1,18 @@ +# +# 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. +# + +org.apache.zeppelin.notebook.repo.GCSNotebookRepo \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/gcs/src/test/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepoTest.java ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/gcs/src/test/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepoTest.java b/zeppelin-plugins/notebookrepo/gcs/src/test/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepoTest.java new file mode 100644 index 0000000..c1fae67 --- /dev/null +++ b/zeppelin-plugins/notebookrepo/gcs/src/test/java/org/apache/zeppelin/notebook/repo/GCSNotebookRepoTest.java @@ -0,0 +1,235 @@ +/* + * 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.notebook.repo; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.fail; + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteInfo; +import org.apache.zeppelin.notebook.Paragraph; +import org.apache.zeppelin.scheduler.Job.Status; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class GCSNotebookRepoTest { + private static final AuthenticationInfo AUTH_INFO = AuthenticationInfo.ANONYMOUS; + + private GCSNotebookRepo notebookRepo; + private Storage storage; + + @Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { "bucketname", Optional.absent(), "gs://bucketname" }, + { "bucketname-with-slash", Optional.absent(), "gs://bucketname-with-slash/" }, + { "bucketname", Optional.of("path/to/dir"), "gs://bucketname/path/to/dir" }, + { "bucketname", Optional.of("trailing/slash"), "gs://bucketname/trailing/slash/" } + }); + } + + @Parameter(0) + public String bucketName; + + @Parameter(1) + public Optional<String> basePath; + + @Parameter(2) + public String uriPath; + + private Note runningNote; + + @Before + public void setUp() throws Exception { + this.runningNote = makeRunningNote(); + + this.storage = LocalStorageHelper.getOptions().getService(); + + System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_GCS_STORAGE_DIR.getVarName(), uriPath); + this.notebookRepo = new GCSNotebookRepo(new ZeppelinConfiguration(), storage); + } + + private static Note makeRunningNote() { + Note note = new Note(); + note.setConfig(ImmutableMap.<String, Object>of("key", "value")); + + Paragraph p = new Paragraph(note, null, null); + p.setText("text"); + p.setStatus(Status.RUNNING); + note.addParagraph(p); + return note; + } + + @Test + public void testList_nonexistent() throws Exception { + assertThat(notebookRepo.list(AUTH_INFO)).isEmpty(); + } + + @Test + public void testList() throws Exception { + createAt(runningNote, "note.json"); + createAt(runningNote, "/note.json"); + createAt(runningNote, "validid/note.json"); + createAt(runningNote, "validid-2/note.json"); + createAt(runningNote, "cannot-be-dir/note.json/foo"); + createAt(runningNote, "cannot/be/nested/note.json"); + + List<NoteInfo> infos = notebookRepo.list(AUTH_INFO); + List<String> noteIds = new ArrayList<>(); + for (NoteInfo info : infos) { + noteIds.add(info.getId()); + } + // Only valid paths are gs://bucketname/path/<noteid>/note.json + assertThat(noteIds).containsExactlyElementsIn(ImmutableList.of("validid", "validid-2")); + } + + @Test + public void testGet_nonexistent() throws Exception { + try { + notebookRepo.get("id", AUTH_INFO); + fail(); + } catch (IOException e) {} + } + + @Test + public void testGet() throws Exception { + create(runningNote); + + // Status of saved running note is removed in get() + Note got = notebookRepo.get(runningNote.getId(), AUTH_INFO); + assertThat(got.getLastParagraph().getStatus()).isEqualTo(Status.ABORT); + + // But otherwise equal + got.getLastParagraph().setStatus(Status.RUNNING); + assertThat(got).isEqualTo(runningNote); + } + + @Test + public void testGet_malformed() throws Exception { + createMalformed("id"); + try { + notebookRepo.get("id", AUTH_INFO); + fail(); + } catch (IOException e) {} + } + + @Test + public void testSave_create() throws Exception { + notebookRepo.save(runningNote, AUTH_INFO); + // Output is saved + assertThat(storage.readAllBytes(makeBlobId(runningNote.getId()))) + .isEqualTo(runningNote.toJson().getBytes("UTF-8")); + } + + @Test + public void testSave_update() throws Exception { + notebookRepo.save(runningNote, AUTH_INFO); + // Change name of runningNote + runningNote.setName("new-name"); + notebookRepo.save(runningNote, AUTH_INFO); + assertThat(storage.readAllBytes(makeBlobId(runningNote.getId()))) + .isEqualTo(runningNote.toJson().getBytes("UTF-8")); + } + + @Test + public void testRemove_nonexistent() throws Exception { + try { + notebookRepo.remove("id", AUTH_INFO); + fail(); + } catch (IOException e) {} + } + + @Test + public void testRemove() throws Exception { + create(runningNote); + notebookRepo.remove(runningNote.getId(), AUTH_INFO); + assertThat(storage.get(makeBlobId(runningNote.getId()))).isNull(); + } + + private String makeName(String relativePath) { + if (basePath.isPresent()) { + return basePath.get() + "/" + relativePath; + } else { + return relativePath; + } + } + + private BlobId makeBlobId(String noteId) { + return BlobId.of(bucketName, makeName(noteId + "/note.json")); + } + + private void createAt(Note note, String relativePath) throws IOException { + BlobId id = BlobId.of(bucketName, makeName(relativePath)); + BlobInfo info = BlobInfo.newBuilder(id).setContentType("application/json").build(); + storage.create(info, note.toJson().getBytes("UTF-8")); + } + + private void create(Note note) throws IOException { + BlobInfo info = BlobInfo.newBuilder(makeBlobId(note.getId())) + .setContentType("application/json") + .build(); + storage.create(info, note.toJson().getBytes("UTF-8")); + } + + private void createMalformed(String noteId) throws IOException { + BlobInfo info = BlobInfo.newBuilder(makeBlobId(noteId)) + .setContentType("application/json") + .build(); + storage.create(info, "{ invalid-json }".getBytes("UTF-8")); + } + + /* These tests test path parsing for illegal paths, and do not use the parameterized vars */ + + @Test + public void testInitialization_pathNotSet() throws Exception { + try { + System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_GCS_STORAGE_DIR.getVarName(), ""); + new GCSNotebookRepo(new ZeppelinConfiguration(), storage); + fail(); + } catch (IOException e) {} + } + + @Test + public void testInitialization_malformedPath() throws Exception { + try { + System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_GCS_STORAGE_DIR.getVarName(), "foo"); + new GCSNotebookRepo(new ZeppelinConfiguration(), storage); + fail(); + } catch (IOException e) {} + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3eea57ab/zeppelin-plugins/notebookrepo/git/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-plugins/notebookrepo/git/pom.xml b/zeppelin-plugins/notebookrepo/git/pom.xml new file mode 100644 index 0000000..6f4ce8c --- /dev/null +++ b/zeppelin-plugins/notebookrepo/git/pom.xml @@ -0,0 +1,71 @@ +<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>zengine-plugins-parent</artifactId> + <groupId>org.apache.zeppelin</groupId> + <version>0.9.0-SNAPSHOT</version> + <relativePath>../../../zeppelin-plugins</relativePath> + </parent> + + <groupId>org.apache.zeppelin</groupId> + <artifactId>notebookrepo-git</artifactId> + <packaging>jar</packaging> + <version>0.9.0-SNAPSHOT</version> + <name>Zeppelin: Plugin GitNotebookRepo</name> + <description>NotebookRepo implementation based on Git</description> + + <properties> + <eclipse.jgit.version>4.5.4.201711221230-r</eclipse.jgit.version> + <google.truth.version>0.27</google.truth.version> + <plugin.name>NotebookRepo/GitNotebookRepo</plugin.name> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.zeppelin</groupId> + <artifactId>notebookrepo-vfs</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.eclipse.jgit</groupId> + <artifactId>org.eclipse.jgit</artifactId> + <version>${eclipse.jgit.version}</version> + </dependency> + + <dependency> + <groupId>com.google.truth</groupId> + <artifactId>truth</artifactId> + <version>${google.truth.version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> +</project>