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 13374bd  [ZEPPELIN-4652]. Support OSSNotebookRepo of Aliyun
13374bd is described below

commit 13374bd6d94751f731d2debcad5d41f341b6f938
Author: Jeff Zhang <zjf...@apache.org>
AuthorDate: Sun Mar 1 18:05:55 2020 +0800

    [ZEPPELIN-4652]. Support OSSNotebookRepo of Aliyun
    
    ### What is this PR for?
    This PR is to support Aliyun's OSS as NotebookRepo. It just add one plugin 
module `oss` and use OSS's SDK for all the note related operation. 
Unfortunately, I didn't found way to do unit test, I only tested it manually in 
my local box.
    
    ### What type of PR is it?
    [ Feature]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    * https://issues.apache.org/jira/browse/ZEPPELIN-4652
    
    ### 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 #3673 from zjffdu/ZEPPELIN-4652 and squashes the following commits:
    
    16f57a85b [Jeff Zhang] [ZEPPELIN-4652]. Support OSSNotebookRepo of Aliyun
---
 conf/zeppelin-site.xml.template                    |  36 ++++
 docs/_includes/themes/zeppelin/_navigation.html    |   1 +
 docs/setup/storage/storage.md                      |  54 ++++++
 .../zeppelin/conf/ZeppelinConfiguration.java       |  20 ++
 zeppelin-plugins/notebookrepo/oss/pom.xml          |  58 ++++++
 .../zeppelin/notebook/repo/OSSNotebookRepo.java    | 215 +++++++++++++++++++++
 zeppelin-plugins/pom.xml                           |   1 +
 7 files changed, 385 insertions(+)

diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index b7c3728..f5cd379 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -193,6 +193,42 @@
 </property>
 -->
 
+<!-- Aliyun OSS notebook storage -->
+<!-- Creates the following directory structure: 
oss://{bucket}/{notebook_dir}/note_path -->
+<!--
+
+<property>
+  <name>zeppelin.notebook.oss.bucket</name>
+  <value>zeppelin</value>
+  <description>bucket name for notebook storage</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.oss.endpoint</name>
+  <value>http://oss-cn-hangzhou.aliyuncs.com</value>
+  <description>endpoint for oss bucket</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.oss.accesskeyid</name>
+  <value></value>
+  <description>Access key id for your OSS account</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.oss.accesskeysecret</name>
+  <value></value>
+  <description>Access key secret for your OSS account</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.storage</name>
+  <value>org.apache.zeppelin.notebook.repo.OSSNotebookRepo</value>
+  <description>notebook persistence layer implementation</description>
+</property>
+
+-->
+
 <!-- If using Azure for storage use the following settings -->
 <!--
 <property>
diff --git a/docs/_includes/themes/zeppelin/_navigation.html 
b/docs/_includes/themes/zeppelin/_navigation.html
index 0a2563f..0940863 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -107,6 +107,7 @@
                 <li><a 
href="{{BASE_PATH}}/setup/storage/storage.html#notebook-storage-in-local-git-repository">Git
 Storage</a></li>
                 <li><a 
href="{{BASE_PATH}}/setup/storage/storage.html#notebook-storage-in-s3">S3 
Storage</a></li>
                 <li><a 
href="{{BASE_PATH}}/setup/storage/storage.html#notebook-storage-in-azure">Azure 
Storage</a></li>
+                <li><a 
href="{{BASE_PATH}}/setup/storage/storage.html#notebook-storage-in-oss">OSS 
Storage</a></li>
                 <li><a 
href="{{BASE_PATH}}/setup/storage/storage.html#notebook-storage-in-zeppelinhub">ZeppelinHub
 Storage</a></li>
                 <li><a 
href="{{BASE_PATH}}/setup/storage/storage.html#notebook-storage-in-mongodb">MongoDB
 Storage</a></li>
                 <li role="separator" class="divider"></li>
diff --git a/docs/setup/storage/storage.md b/docs/setup/storage/storage.md
index 6ef3453..c1e0997 100644
--- a/docs/setup/storage/storage.md
+++ b/docs/setup/storage/storage.md
@@ -34,6 +34,7 @@ There are few notebook storage systems available for a use 
out of the box:
   * storage using Amazon S3 service - `S3NotebookRepo`
   * storage using Azure service - `AzureNotebookRepo`
   * storage using Google Cloud Storage - `GCSNotebookRepo`
+  * storage using Aliyun OSS - `OSSNotebookRepo`
   * storage using MongoDB - `MongoNotebookRepo`
   * storage using GitHub - `GitHubNotebookRepo`
 
@@ -372,6 +373,59 @@ file for authentication with GCS, update the following 
property :
 
 
 </br>
+
+## Notebook Storage in OSS <a name="OSS"></a>
+
+Notebooks may be stored in Aliyun OSS.
+
+</br>
+The following folder structure will be created in OSS:
+
+```
+oss://bucket_name/{noteboo_dir}/note_path
+```
+
+
+And you should configure oss related properties in file **zeppelin-site.xml**.
+
+```xml
+<property>
+  <name>zeppelin.notebook.oss.bucket</name>
+  <value>zeppelin</value>
+  <description>bucket name for notebook storage</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.oss.endpoint</name>
+  <value>http://oss-cn-hangzhou.aliyuncs.com</value>
+  <description>endpoint for oss bucket</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.oss.accesskeyid</name>
+  <value></value>
+  <description>Access key id for your OSS account</description>
+</property>
+
+<property>
+  <name>zeppelin.notebook.oss.accesskeysecret</name>
+  <value></value>
+  <description>Access key secret for your OSS account</description>
+</property>
+
+```
+
+Uncomment the next property for use OSSNotebookRepo class:
+
+```xml
+<property>
+  <name>zeppelin.notebook.storage</name>
+  <value>org.apache.zeppelin.notebook.repo.OSSNotebookRepo</value>
+  <description>notebook persistence layer implementation</description>
+</property>
+```
+
+</br>
 ## Notebook Storage in ZeppelinHub  <a name="ZeppelinHub"></a>
 
 ZeppelinHub storage layer allows out of the box connection of Zeppelin 
instance with your ZeppelinHub account. First of all, you need to either 
comment out the following  property in **zeppelin-site.xml**:
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 dbdb614..afeaa4e 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
@@ -474,6 +474,22 @@ public class ZeppelinConfiguration extends 
XMLConfiguration {
     return getString(ConfVars.ZEPPELIN_NOTEBOOK_S3_SIGNEROVERRIDE);
   }
 
+  public String getOSSBucketName() {
+    return getString(ConfVars.ZEPPELIN_NOTEBOOK_OSS_BUCKET);
+  }
+
+  public String getOSSEndpoint() {
+    return getString(ConfVars.ZEPPELIN_NOTEBOOK_OSS_ENDPOINT);
+  }
+
+  public String getOSSAccessKeyId() {
+    return getString(ConfVars.ZEPPELIN_NOTEBOOK_OSS_ACCESSKEYID);
+  }
+
+  public String getOSSAccessKeySecret() {
+    return getString(ConfVars.ZEPPELIN_NOTEBOOK_OSS_ACCESSKEYSECRET);
+  }
+
   public String getMongoUri() {
     return getString(ConfVars.ZEPPELIN_NOTEBOOK_MONGO_URI);
   }
@@ -873,6 +889,10 @@ public class ZeppelinConfiguration extends 
XMLConfiguration {
     ZEPPELIN_NOTEBOOK_S3_KMS_KEY_REGION("zeppelin.notebook.s3.kmsKeyRegion", 
null),
     ZEPPELIN_NOTEBOOK_S3_SSE("zeppelin.notebook.s3.sse", false),
     ZEPPELIN_NOTEBOOK_S3_SIGNEROVERRIDE("zeppelin.notebook.s3.signerOverride", 
null),
+    ZEPPELIN_NOTEBOOK_OSS_BUCKET("zeppelin.notebook.oss.bucket", "zeppelin"),
+    ZEPPELIN_NOTEBOOK_OSS_ENDPOINT("zeppelin.notebook.oss.endpoint", 
"http://oss-cn-hangzhou.aliyuncs.com";),
+    ZEPPELIN_NOTEBOOK_OSS_ACCESSKEYID("zeppelin.notebook.oss.accesskeyid", 
null),
+    
ZEPPELIN_NOTEBOOK_OSS_ACCESSKEYSECRET("zeppelin.notebook.oss.accesskeysecret", 
null),
     
ZEPPELIN_NOTEBOOK_AZURE_CONNECTION_STRING("zeppelin.notebook.azure.connectionString",
 null),
     ZEPPELIN_NOTEBOOK_AZURE_SHARE("zeppelin.notebook.azure.share", "zeppelin"),
     ZEPPELIN_NOTEBOOK_AZURE_USER("zeppelin.notebook.azure.user", "user"),
diff --git a/zeppelin-plugins/notebookrepo/oss/pom.xml 
b/zeppelin-plugins/notebookrepo/oss/pom.xml
new file mode 100644
index 0000000..9176009
--- /dev/null
+++ b/zeppelin-plugins/notebookrepo/oss/pom.xml
@@ -0,0 +1,58 @@
+<?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>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-oss</artifactId>
+    <packaging>jar</packaging>
+    <version>0.9.0-SNAPSHOT</version>
+    <name>Zeppelin: Plugin OSSNotebookRepo</name>
+    <description>NotebookRepo implementation based on Aliyun OSS</description>
+
+    <properties>
+        <oss.version>3.8.0</oss.version>
+        <plugin.name>NotebookRepo/OSSNotebookRepo</plugin.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>${oss.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/zeppelin-plugins/notebookrepo/oss/src/main/java/org/apache/zeppelin/notebook/repo/OSSNotebookRepo.java
 
b/zeppelin-plugins/notebookrepo/oss/src/main/java/org/apache/zeppelin/notebook/repo/OSSNotebookRepo.java
new file mode 100644
index 0000000..a7f1cb2
--- /dev/null
+++ 
b/zeppelin-plugins/notebookrepo/oss/src/main/java/org/apache/zeppelin/notebook/repo/OSSNotebookRepo.java
@@ -0,0 +1,215 @@
+/*
+ * 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.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.CopyObjectRequest;
+import com.aliyun.oss.model.DeleteObjectsRequest;
+import com.aliyun.oss.model.ListObjectsRequest;
+import com.aliyun.oss.model.OSSObject;
+import com.aliyun.oss.model.OSSObjectSummary;
+import com.aliyun.oss.model.ObjectListing;
+import com.aliyun.oss.model.PutObjectRequest;
+import org.apache.commons.io.IOUtils;
+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;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * NotebookRepo for Aliyun OSS (https://cn.aliyun.com/product/oss)
+ */
+public class OSSNotebookRepo implements NotebookRepo {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(OSSNotebookRepo.class);
+
+  private OSS ossClient;
+  private String bucketName;
+  private String rootFolder;
+
+  public OSSNotebookRepo() {
+  }
+
+  public void init(ZeppelinConfiguration conf) throws IOException {
+    String endpoint = conf.getOSSEndpoint();
+    bucketName = conf.getOSSBucketName();
+    rootFolder = conf.getNotebookDir();
+    // rootFolder is part of OSS key which doesn't start with '/'
+    if (rootFolder.startsWith("/")) {
+      rootFolder = rootFolder.substring(1);
+    }
+    String accessKeyId = conf.getOSSAccessKeyId();
+    String accessKeySecret = conf.getOSSAccessKeySecret();
+    this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, 
accessKeySecret);
+  }
+
+  @Override
+  public Map<String, NoteInfo> list(AuthenticationInfo subject) throws 
IOException {
+    Map<String, NoteInfo> notesInfo = new HashMap<>();
+    final int maxKeys = 200;
+    String nextMarker = null;
+    ObjectListing objectListing = null;
+    do {
+      objectListing = ossClient.listObjects(
+              new ListObjectsRequest(bucketName)
+                      .withPrefix(rootFolder + "/")
+                      .withMarker(nextMarker)
+                      .withMaxKeys(maxKeys));
+      List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
+      for (OSSObjectSummary s : sums) {
+        if (s.getKey().endsWith(".zpln")) {
+          try {
+            String noteId = getNoteId(s.getKey());
+            String notePath = getNotePath(rootFolder, s.getKey());
+            notesInfo.put(noteId, new NoteInfo(noteId, notePath));
+          } catch (IOException e) {
+            LOGGER.warn(e.getMessage());
+          }
+        } else {
+          LOGGER.debug("Skip invalid note file: " + s.getKey());
+        }
+      }
+      nextMarker = objectListing.getNextMarker();
+    } while (objectListing.isTruncated());
+
+    return notesInfo;
+  }
+
+  @Override
+  public Note get(String noteId, String notePath, AuthenticationInfo subject) 
throws IOException {
+    OSSObject ossObject = ossClient.getObject(bucketName,
+            rootFolder + "/" + buildNoteFileName(noteId, notePath));
+    InputStream in = null;
+    try {
+      in = ossObject.getObjectContent();
+      return Note.fromJson(IOUtils.toString(in));
+    } finally {
+      if (in != null) {
+        in.close();
+      }
+    }
+  }
+
+  @Override
+  public void save(Note note, AuthenticationInfo subject) throws IOException {
+    String content = note.toJson();
+    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName,
+            rootFolder + "/" + buildNoteFileName(note.getId(), note.getPath()),
+            new ByteArrayInputStream(content.getBytes()));
+    ossClient.putObject(putObjectRequest);
+  }
+
+  @Override
+  public void move(String noteId, String notePath, String newNotePath,
+                   AuthenticationInfo subject) throws IOException {
+    String sourceKey = rootFolder + "/" + buildNoteFileName(noteId, notePath);
+    String destKey = rootFolder + "/" + buildNoteFileName(noteId, newNotePath);
+    CopyObjectRequest copyObjectRequest = new CopyObjectRequest(bucketName,
+            sourceKey, bucketName, destKey);
+    ossClient.copyObject(copyObjectRequest);
+    ossClient.deleteObject(bucketName, sourceKey);
+  }
+
+  @Override
+  public void move(String folderPath, String newFolderPath, AuthenticationInfo 
subject) {
+    final int maxKeys = 200;
+    String nextMarker = null;
+    ObjectListing objectListing = null;
+    do {
+      objectListing = ossClient.listObjects(
+              new ListObjectsRequest(bucketName)
+                      .withPrefix(rootFolder + folderPath + "/")
+                      .withMarker(nextMarker)
+                      .withMaxKeys(maxKeys));
+      List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
+      for (OSSObjectSummary s : sums) {
+        if (s.getKey().endsWith(".zpln")) {
+          try {
+            String noteId = getNoteId(s.getKey());
+            String notePath = getNotePath(rootFolder, s.getKey());
+            String newNotePath = newFolderPath + 
notePath.substring(folderPath.length());
+            move(noteId, notePath, newNotePath, subject);
+          } catch (IOException e) {
+            LOGGER.warn(e.getMessage());
+          }
+        } else {
+          LOGGER.debug("Skip invalid note file: " + s.getKey());
+        }
+      }
+      nextMarker = objectListing.getNextMarker();
+    } while (objectListing.isTruncated());
+
+  }
+
+  @Override
+  public void remove(String noteId, String notePath, AuthenticationInfo 
subject)
+      throws IOException {
+    ossClient.deleteObject(bucketName, rootFolder + "/" + 
buildNoteFileName(noteId, notePath));
+  }
+
+  @Override
+  public void remove(String folderPath, AuthenticationInfo subject) {
+    String nextMarker = null;
+    ObjectListing objectListing = null;
+    do {
+      ListObjectsRequest listObjectsRequest = new 
ListObjectsRequest(bucketName)
+              .withPrefix(rootFolder + folderPath + "/")
+              .withMarker(nextMarker);
+      objectListing = ossClient.listObjects(listObjectsRequest);
+      if (objectListing.getObjectSummaries().size() > 0) {
+        List<String> keys = new ArrayList();
+        for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
+          keys.add(s.getKey());
+        }
+        DeleteObjectsRequest deleteObjectsRequest = new 
DeleteObjectsRequest(bucketName).withKeys(keys);
+        ossClient.deleteObjects(deleteObjectsRequest);
+      }
+
+      nextMarker = objectListing.getNextMarker();
+    } while (objectListing.isTruncated());
+  }
+
+  @Override
+  public void close() {
+    ossClient.shutdown();
+  }
+
+  @Override
+  public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo 
subject) {
+    LOGGER.warn("Method not implemented");
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void updateSettings(Map<String, String> settings, AuthenticationInfo 
subject) {
+    LOGGER.warn("Method not implemented");
+  }
+
+}
diff --git a/zeppelin-plugins/pom.xml b/zeppelin-plugins/pom.xml
index fcd3957..5629f16 100644
--- a/zeppelin-plugins/pom.xml
+++ b/zeppelin-plugins/pom.xml
@@ -43,6 +43,7 @@
         <module>notebookrepo/zeppelin-hub</module>
         <module>notebookrepo/filesystem</module>
         <module>notebookrepo/mongo</module>
+        <module>notebookrepo/oss</module>
 
         <module>launcher/k8s-standard</module>
         <module>launcher/cluster</module>

Reply via email to