This commit is contained in:
Jesang Yoon 2016-05-08 23:48:51 +09:00
commit 7525167b18
109 changed files with 1842 additions and 709 deletions

2
.gitignore vendored
View file

@ -39,6 +39,8 @@ zeppelin-web/bower_components
**nbproject/
**node/
#R
/r/lib/
# project level
/logs/

View file

@ -81,11 +81,17 @@ before_script:
script:
- mvn $TEST_FLAG $PROFILE -B $TEST_PROJECTS
after_success:
- echo "Travis exited with ${TRAVIS_TEST_RESULT}"
after_failure:
- echo "Travis exited with ${TRAVIS_TEST_RESULT}"
- cat target/rat.txt
- cat zeppelin-server/target/rat.txt
- cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.log
- cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.out
- cat zeppelin-web/npm-debug.log
after_script:
- ./testing/stopSparkCluster.sh $SPARK_VER $HADOOP_VER

106
README.md
View file

@ -1,4 +1,4 @@
#Zeppelin
#Zeppelin
**Documentation:** [User Guide](http://zeppelin.incubator.apache.org/docs/latest/index.html)<br/>
**Mailing Lists:** [User and Dev mailing list](http://zeppelin.incubator.apache.org/community.html)<br/>
@ -21,15 +21,15 @@ To know more about Zeppelin, visit our web site [http://zeppelin.incubator.apach
* Java 1.7
* Tested on Mac OSX, Ubuntu 14.X, CentOS 6.X
* Maven (if you want to build from the source code)
* Node.js Package Manager
* Node.js Package Manager (npm)
## Getting Started
### Before Build
If you don't have requirements prepared, install it.
If you don't have requirements prepared, install it.
(The installation method may vary according to your environment, example is for Ubuntu.)
```
```sh
sudo apt-get update
sudo apt-get install git
sudo apt-get install openjdk-7-jdk
@ -42,10 +42,10 @@ sudo tar -zxf apache-maven-3.3.3-bin.tar.gz -C /usr/local/
sudo ln -s /usr/local/apache-maven-3.3.3/bin/mvn /usr/local/bin/mvn
```
_Notes:_
_Notes:_
- Ensure node is installed by running `node --version`
- Ensure maven is running version 3.1.x or higher with `mvn -version`
- Configure maven to use more memory than usual by ```export MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=1024m"```
- Configure maven to use more memory than usual by `export MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=1024m"`
### Build
If you want to build Zeppelin from the source, please first clone this repository, then:
@ -61,7 +61,7 @@ Each Interpreter requires different Options.
To build with a specific Spark version, Hadoop version or specific features, define one or more of the following profiles and options:
##### -Pspark-[version]
##### `-Pspark-[version]`
Set spark major version
@ -84,7 +84,7 @@ Available profiles are
minor version can be adjusted by `-Dspark.version=x.x.x`
##### -Phadoop-[version]
##### `-Phadoop-[version]`
set hadoop major version
@ -101,25 +101,32 @@ Available profiles are
minor version can be adjusted by `-Dhadoop.version=x.x.x`
##### -Pyarn (optional)
##### `-Pyarn` (optional)
enable YARN support for local mode
> YARN for local mode is not supported for Spark v1.5.0 or higher. Set SPARK_HOME instead.
> YARN for local mode is not supported for Spark v1.5.0 or higher. Set `SPARK_HOME` instead.
##### -Ppyspark (optional)
##### `-Ppyspark` (optional)
enable PySpark support for local mode
enable [PySpark](http://spark.apache.org/docs/latest/api/python/) support for local mode.
##### `-Pr` (optional)
##### -Pvendor-repo (optional)
enable [R](https://www.r-project.org/) support with [SparkR](https://spark.apache.org/docs/latest/sparkr.html) integration.
##### `-Psparkr` (optional)
another [R](https://www.r-project.org/) support with [SparkR](https://spark.apache.org/docs/latest/sparkr.html) integration as well as local mode support.
##### `-Pvendor-repo` (optional)
enable 3rd party vendor repository (cloudera)
##### -Pmapr[version] (optional)
##### `-Pmapr[version]` (optional)
For the MapR Hadoop Distribution, these profiles will handle the Hadoop version. As MapR allows different versions
of Spark to be installed, you should specify which version of Spark is installed on the cluster by adding a Spark profile (-Pspark-1.2, -Pspark-1.3, etc.) as needed. For Hive, check the hive/pom.xml and adjust the version installed as well. The correct Maven
For the MapR Hadoop Distribution, these profiles will handle the Hadoop version. As MapR allows different versions of Spark to be installed, you should specify which version of Spark is installed on the cluster by adding a Spark profile (`-Pspark-1.2`, `-Pspark-1.3`, etc.) as needed.
For Hive, check the hive/pom.xml and adjust the version installed as well. The correct Maven
artifacts can be found for every version of MapR at http://doc.mapr.com
Available profiles are
@ -135,7 +142,7 @@ Available profiles are
Here're some examples:
```
```sh
# basic build
mvn clean package -Pspark-1.6 -Phadoop-2.4 -Pyarn -Ppyspark
@ -152,13 +159,13 @@ mvn clean package -Pspark-1.5 -Pmapr50 -DskipTests
#### Ignite Interpreter
```
```sh
mvn clean package -Dignite.version=1.1.0-incubating -DskipTests
```
#### Scalding Interpreter
```
```sh
mvn clean package -Pscalding -DskipTests
```
@ -169,67 +176,80 @@ If you wish to configure Zeppelin option (like port number), configure the follo
./conf/zeppelin-env.sh
./conf/zeppelin-site.xml
```
(You can copy ```./conf/zeppelin-env.sh.template``` into ```./conf/zeppelin-env.sh```.
Same for ```zeppelin-site.xml```.)
(You can copy `./conf/zeppelin-env.sh.template` into `./conf/zeppelin-env.sh`.
Same for `zeppelin-site.xml`.)
#### Setting SPARK_HOME and HADOOP_HOME
Without SPARK_HOME and HADOOP_HOME, Zeppelin uses embedded Spark and Hadoop binaries that you have specified with mvn build option.
If you want to use system provided Spark and Hadoop, export SPARK_HOME and HADOOP_HOME in zeppelin-env.sh
Without `SPARK_HOME` and `HADOOP_HOME`, Zeppelin uses embedded Spark and Hadoop binaries that you have specified with mvn build option.
If you want to use system provided Spark and Hadoop, export `SPARK_HOME` and `HADOOP_HOME` in `zeppelin-env.sh`.
You can use any supported version of spark without rebuilding Zeppelin.
```
```sh
# ./conf/zeppelin-env.sh
export SPARK_HOME=...
export HADOOP_HOME=...
```
#### External cluster configuration
Mesos
# ./conf/zeppelin-env.sh
export MASTER=mesos://...
export ZEPPELIN_JAVA_OPTS="-Dspark.executor.uri=/path/to/spark-*.tgz" or SPARK_HOME="/path/to/spark_home"
export MESOS_NATIVE_LIBRARY=/path/to/libmesos.so
```sh
# ./conf/zeppelin-env.sh
export MASTER=mesos://...
export ZEPPELIN_JAVA_OPTS="-Dspark.executor.uri=/path/to/spark-*.tgz" or SPARK_HOME="/path/to/spark_home"
export MESOS_NATIVE_LIBRARY=/path/to/libmesos.so
```
If you set `SPARK_HOME`, you should deploy spark binary on the same location to all worker nodes. And if you set `spark.executor.uri`, every worker can read that file on its node.
Yarn
# ./conf/zeppelin-env.sh
export SPARK_HOME=/path/to/spark_dir
```sh
# ./conf/zeppelin-env.sh
export SPARK_HOME=/path/to/spark_dir
```
### Run
./bin/zeppelin-daemon.sh start
browse localhost:8080 in your browser.
```sh
./bin/zeppelin-daemon.sh start
```
And browse [localhost:8080](localhost:8080) in your browser.
For configuration details check __./conf__ subdirectory.
For configuration details check __`./conf`__ subdirectory.
### Package
To package the final distribution including the compressed archive, run:
mvn clean package -Pbuild-distr
```sh
mvn clean package -Pbuild-distr
```
To build a distribution with specific profiles, run:
mvn clean package -Pbuild-distr -Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark
```sh
mvn clean package -Pbuild-distr -Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark
```
The profiles `-Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark` can be adjusted if you wish to build to a specific spark versions, or omit support such as `yarn`.
The archive is generated under _zeppelin-distribution/target_ directory
The archive is generated under _`zeppelin-distribution/target`_ directory
###Run end-to-end tests
Zeppelin comes with a set of end-to-end acceptance tests driving headless selenium browser
#assumes zeppelin-server running on localhost:8080 (use -Durl=.. to override)
mvn verify
#or take care of starting\stoping zeppelin-server from packaged _zeppelin-distribuion/target_
mvn verify -P using-packaged-distr
```sh
# assumes zeppelin-server running on localhost:8080 (use -Durl=.. to override)
mvn verify
# or take care of starting/stoping zeppelin-server from packaged zeppelin-distribuion/target
mvn verify -P using-packaged-distr
```
[![Analytics](https://ga-beacon.appspot.com/UA-45176241-4/apache/incubator-zeppelin/README.md?pixel)](https://github.com/igrigorik/ga-beacon)

View file

@ -87,4 +87,4 @@ if [[ ! -d "${ZEPPELIN_NOTEBOOK_DIR}" ]]; then
$(mkdir -p "${ZEPPELIN_NOTEBOOK_DIR}")
fi
$(exec $ZEPPELIN_RUNNER $JAVA_OPTS -cp $ZEPPELIN_CLASSPATH_OVERRIDES:$CLASSPATH $ZEPPELIN_SERVER "$@")
exec $ZEPPELIN_RUNNER $JAVA_OPTS -cp $ZEPPELIN_CLASSPATH_OVERRIDES:$CLASSPATH $ZEPPELIN_SERVER "$@"

View file

@ -302,10 +302,10 @@ public class InterpreterLogicTest {
}
private <A> scala.collection.immutable.List<A> toScalaList(java.util.List<A> list) {
return scala.collection.JavaConversions.asScalaIterable(list).toList();
return scala.collection.JavaConversions.collectionAsScalaIterable(list).toList();
}
private <A> java.util.List<A> toJavaList(scala.collection.immutable.List<A> list){
return scala.collection.JavaConversions.asJavaList(list);
return scala.collection.JavaConversions.seqAsJavaList(list);
}
}

View file

@ -37,4 +37,4 @@ shiro.loginUrl = /api/login
# To enfore security, comment the line below and uncomment the next one
/api/version = anon
/** = anon
#/** = authc
#/** = authcBasic

View file

@ -1,6 +1,6 @@
# This is the default format.
# This is the default format.
# For more see: http://jekyllrb.com/docs/permalinks/
permalink: /:categories/:year/:month/:day/:title
permalink: /:categories/:year/:month/:day/:title
exclude: [".rvmrc", ".rbenv-version", "README.md", "Rakefile", "changelog.md", "vendor", "node_modules", "scss"]
pygments: true
@ -9,7 +9,7 @@ redcarpet:
extensions: ["tables"]
encoding: utf-8
# Themes are encouraged to use these universal variables
# Themes are encouraged to use these universal variables
# so be sure to set them if your theme uses them.
#
title : Apache Zeppelin (incubating)
@ -24,7 +24,7 @@ author :
ZEPPELIN_VERSION : 0.6.0-incubating-SNAPSHOT
# The production_url is only used when full-domain names are needed
# such as sitemap.txt
# such as sitemap.txt
# Most places will/should use BASE_PATH to make the urls
#
# If you have set a CNAME (pages.github.com) set your custom domain here.
@ -42,11 +42,11 @@ JB :
# however this value will be dynamically changed depending on your deployment situation.
#
# CNAME (http://yourcustomdomain.com)
# DO NOT SET BASE_PATH
# DO NOT SET BASE_PATH
# (urls will be prefixed with "/" and work relatively)
#
# GitHub Pages (http://username.github.io)
# DO NOT SET BASE_PATH
# DO NOT SET BASE_PATH
# (urls will be prefixed with "/" and work relatively)
#
# GitHub Project Pages (http://username.github.io/project-name)
@ -65,7 +65,7 @@ JB :
# ex: [BASE_PATH]/assets/themes/[THEME-NAME]
#
# Override this by defining an absolute path to assets here.
# ex:
# ex:
# http://s3.amazonaws.com/yoursite/themes/watermelon
# /assets
#
@ -97,42 +97,41 @@ JB :
num_posts: 5
width: 580
colorscheme: light
# Settings for analytics helper
# Set 'provider' to the analytics provider you want to use.
# Set 'provider' to false to turn analytics off globally.
#
#
analytics :
provider : google_universal
google_classic :
google_classic :
tracking_id : 'UA-45176241-2'
google_universal :
google_universal :
tracking_id : 'UA-45176241-5'
domain : 'zeppelin.incubator.apache.org'
getclicky :
site_id :
site_id :
mixpanel :
token : '_MIXPANEL_TOKEN_'
piwik :
baseURL : 'myserver.tld/piwik' # Piwik installation address (without protocol)
idsite : '1' # the id of the site on Piwik
# Settings for sharing helper.
# Settings for sharing helper.
# Sharing is for things like tweet, plusone, like, reddit buttons etc.
# Set 'provider' to the sharing provider you want to use.
# Set 'provider' to false to turn sharing off globally.
#
sharing :
provider : false
# Settings for all other include helpers can be defined by creating
# Settings for all other include helpers can be defined by creating
# a hash with key named for the given helper. ex:
#
# pages_list :
# provider : "custom"
# provider : "custom"
#
# Setting any helper's provider to 'custom' will bypass the helper code
# and include your custom code. Your custom file must be defined at:
# ./_includes/custom/[HELPER]
# where [HELPER] is the name of the helper you are overriding.

View file

@ -31,6 +31,8 @@
<!-- li><span><b>Tutorial</b><span></li -->
<li><a href="{{BASE_PATH}}/tutorial/tutorial.html">Tutorial</a></li>
<li role="separator" class="divider"></li>
<li><a href="{{BASE_PATH}}/ui_layout/zeppelin_layout.html">UI Layout</a></li>
<li role="separator" class="divider"></li>
<!-- li><span><b>Guide</b><span></li -->
<li><a href="{{BASE_PATH}}/manual/dynamicform.html">Dynamic Form</a></li>
<li><a href="{{BASE_PATH}}/manual/publish.html">Publish your Paragraph</a></li>
@ -70,7 +72,8 @@
<li><a href="{{BASE_PATH}}/displaysystem/display.html">Text</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/display.html#html">Html</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/table.html">Table</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/angular.html">Angular</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/back-end-angular.html">Angular (backend API)</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/front-end-angular.html">Angular (frontend API)</a></li>
</ul>
</li>
<li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

View file

@ -4,7 +4,7 @@ title : Atom Feed
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ site.title }}</title>
<link href="{{ site.production_url }}/{{ site.atom_path }}" rel="self"/>
<link href="{{ site.production_url }}"/>
@ -24,5 +24,5 @@ title : Atom Feed
<content type="html">{{ post.content | xml_escape }}</content>
</entry>
{% endfor %}
</feed>
</feed>

View file

@ -12,20 +12,20 @@ Apache Zeppelin (incubating) is an [Apache2 License](http://www.apache.org/licen
Any contributions to Zeppelin (Source code, Documents, Image, Website) means you agree with license all your contributions as Apache2 License.
## Setting up
Here are some tools you will need to build and test Zeppelin.
Here are some tools you will need to build and test Zeppelin.
#### Software Configuration Management ( SCM )
Since Zeppelin uses Git for it's SCM system, you need git client installed in your development machine.
Since Zeppelin uses Git for it's SCM system, you need git client installed in your development machine.
#### Integrated Development Environment ( IDE )
You are free to use whatever IDE you prefer, or your favorite command line editor.
You are free to use whatever IDE you prefer, or your favorite command line editor.
### Build Tools
To build the code, install
* Oracle Java 7
* Apache Maven

View file

@ -22,7 +22,7 @@ limitations under the License.
### What is Zeppelin Interpreter
Zeppelin Interpreter is a language backend. For example to use scala code in Zeppelin, you need scala interpreter.
Every Interpreter belongs to an InterpreterGroup.
Every Interpreter belongs to an InterpreterGroup.
Interpreters in the same InterpreterGroup can reference each other. For example, SparkSqlInterpreter can reference SparkInterpreter to get SparkContext from it while they're in the same group.
<img class="img-responsive" style="width:50%; border: 1px solid #ecf0f1;" height="auto" src="/assets/themes/zeppelin/img/interpreter.png" />

View file

@ -1,7 +1,7 @@
---
layout: page
title: "Angular Display System"
description: ""
title: "Angular (backend API)"
description: "Angular (backend API)"
group: display
---
<!--
@ -20,7 +20,7 @@ limitations under the License.
{% include JB/setup %}
## Angular Display System in Zeppelin
## Back-end Angular API in Zeppelin
Angular display system treats output as a view template for [AngularJS](https://angularjs.org/).
It compiles templates and displays them inside of Zeppelin.
@ -92,12 +92,12 @@ When the button is clicked, you'll see both `run` and `numWatched` are increment
<img src="/assets/themes/zeppelin/img/screenshots/display_angular3.png" width="60%" />
## Let's make it Simpler and more Intuitive
In this section, we will introduce a simpler and more intuitive way of using **Angular Display System** in Zeppelin.
In this section, we will introduce a simpler and more intuitive way of using **Angular Display System** in Zeppelin.
### How can we use it?
Here are some usages.
Here are some usages.
#### Import
#### Import
##### - In notebook scope
```scala
@ -141,11 +141,11 @@ import AngularElem._
<div></div>.model("myModel").display
// bind model with initial value
<div></div>.model("myModel", initialValue).display
<div></div>.model("myModel", initialValue).display
```
#### Interact with Model
```scala
```scala
// read model
AngularModel("myModel")()
@ -155,7 +155,7 @@ AngularModel("myModel", "newValue")
<br/>
### Example: Basic Usage
Using the above basic usages, you can apply them like below examples.
Using the above basic usages, you can apply them like below examples.
#### Display Elements
@ -195,7 +195,7 @@ AngularModel("myModel", "New value")
### Example: String Converter
Using below example, you can convert the lowercase string to uppercase.
{% raw %}
```scala
// clear previously created angular object.
@ -215,5 +215,3 @@ val button = <div class="btn btn-success btn-sm">Convert</div>.onClick{() =>
{% endraw %}
<img src="../assets/themes/zeppelin/img/docs-img/string-converter-angular.gif" width="70%">

View file

@ -0,0 +1,159 @@
---
layout: page
title: "Angular (frontend API)"
description: "Angular (frontend API)"
group: display
---
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
{% include JB/setup %}
## Front-end Angular API in Zeppelin
In addition to the back-end API to handle Angular objects binding, Zeppelin also exposes a simple AngularJS **`z`** object on the front-end side to expose the same capabilities.
This **`z`** object is accessible in the Angular isolated scope for each paragraph.
<br />
### Bind / Unbind Variables
Through the **`z`**, you can bind / unbind variables to **AngularJS view**
Bind a value to an angular object and a **mandatory** target paragraph:
```html
%angular
<form class="form-inline">
<div class="form-group">
<label for="superheroId">Super Hero: </label>
<input type="text" class="form-control" id="superheroId" placeholder="Superhero name ..." ng-model="superhero"></input>
</div>
<button type="submit" class="btn btn-primary" ng-click="z.angularBind('superhero',superhero,'20160222-232336_1472609686')"> Bind</button>
</form>
```
<img src="/assets/themes/zeppelin/img/screenshots/z_angularBind.gif" />
<hr/>
Unbind/remove a value from angular object and a **mandatory** target paragraph:
```html
%angular
<form class="form-inline">
<button type="submit" class="btn btn-primary" ng-click="z.angularUnbind('superhero','20160222-232336_1472609686')"> UnBind</button>
</form>
```
<img src="/assets/themes/zeppelin/img/screenshots/z_angularUnbind.gif" />
The signature for the **`z.angularBind() / z.angularUnbind()`** functions are:
```javascript
z.angularBind(angularObjectName, angularObjectValue, paragraphId);
z.angularUnbind(angularObjectName, angularObjectValue, paragraphId);
```
All the parameters are mandatory.
<br />
### Run Paragraph
You can also trigger paragraph execution by calling **`z.runParagraph()`** function passing the appropriate paragraphId:
```html
%angular
<form class="form-inline">
<div class="form-group">
<label for="paragraphId">Paragraph Id: </label>
<input type="text" class="form-control" id="paragraphId" placeholder="Paragraph Id ..." ng-model="paragraph"></input>
</div>
<button type="submit" class="btn btn-primary" ng-click="z.runParagraph(paragraph)"> Run Paragraph</button>
</form>
```
<img src="/assets/themes/zeppelin/img/screenshots/z_runParagraph.gif" />
<br />
### Overriding dynamic form with Angular Object
The front-end Angular Interaction API has been designed to offer richer form capabilities and variable binding. With the existing **Dynamic Form** system you can already create input text, select and checkbox forms but the choice is rather limited and the look & feel cannot be changed.
The idea is to create a custom form using plain HTML/AngularJS code and bind actions on this form to push/remove Angular variables to targeted paragraphs using this new API.
Consequently if you use the **Dynamic Form** syntax in a paragraph and there is a bound Angular object having the same name as the _${formName}_, the Angular object will have higher priority and the **Dynamic Form** will not be displayed. Example:
<img src="/assets/themes/zeppelin/img/screenshots/z_angularJs_overriding_dynamic_form.gif" />
<br />
### Feature matrix comparison
How does the front-end AngularJS API compares to the back-end API ? Below is a comparison matrix for both APIs:
<table>
<thead>
<tr>
<th>Actions</th>
<th>Front-end API</th>
<th>Back-end API</th>
</tr>
</thead>
<tr>
<td>Initiate binding</td>
<td>z.angularbind(var, initialValue, paragraphId)</td>
<td>z.angularBind(var, initialValue)</td>
</tr>
<tr>
<td>Update value</td>
<td>same to ordinary angularjs scope variable, or z.angularbind(var, newValue, paragraphId)</td>
<td>z.angularBind(var, newValue)</td>
</tr>
<tr>
<td>Watching value</td>
<td>same to ordinary angularjs scope variable</td>
<td>z.angularWatch(var, (oldVal, newVal) => ...)</td>
</tr>
<tr>
<td>Destroy binding</td>
<td>z.angularUnbind(var, paragraphId)</td>
<td>z.angularUnbind(var)</td>
</tr>
<tr>
<td>Executing Paragraph</td>
<td>z.runParagraph(paragraphId)</td>
<td>z.run(paragraphId)</td>
</tr>
<tbody>
<tbody>
</table>
Both APIs are pretty similar, except for value watching where it is done naturally by AngularJS internals on the front-end and by user custom watcher functions in the back-end.
There is also a slight difference in term of scope. Front-end API limits the Angular object binding to a paragraph scope whereas back-end API allows you to bind an Angular object at the global or note scope. This restriction has been designed purposely to avoid Angular object leaks and scope pollution.

View file

@ -76,7 +76,7 @@ Some basic charts are already included in Zeppelin. Visualizations are not limit
#### Pivot chart
With simple drag and drop Zeppelin aggeregates the values and display them in pivot chart. You can easily create chart with multiple aggregated values including sum, count, average, min, max.
With simple drag and drop Zeppelin aggregates the values and display them in pivot chart. You can easily create chart with multiple aggregated values including sum, count, average, min, max.
<div class="row">
<div class="col-md-8">
@ -123,4 +123,4 @@ Join the [Mailing list](./community.html) and report issues on our [Issue tracke
<br />
### Undergoing Incubation
Apache Zeppelin is an effort undergoing [incubation](https://incubator.apache.org/index.html) at The Apache Software Foundation (ASF), sponsored by the Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.
Apache Zeppelin is an effort undergoing [incubation](https://incubator.apache.org/index.html) at The Apache Software Foundation (ASF), sponsored by the Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.

View file

@ -295,4 +295,3 @@ exec bin/zeppelin-daemon.sh upstart
```
bin\zeppelin.cmd
```

View file

@ -41,4 +41,4 @@ So, copying `notebook` and `conf` directory should be enough.
```
bin/zeppelin-daemon.sh start
```
```

View file

@ -21,15 +21,15 @@ limitations under the License.
## Vagrant Virtual Machine for Apache Zeppelin
Apache Zeppelin distribution includes a scripts directory
`scripts/vagrant/zeppelin-dev`
This script creates a virtual machine that launches a repeatable, known set of core dependencies required for developing Zeppelin. It can also be used to run an existing Zeppelin build if you don't plan to build from source.
For PySpark users, this script includes several helpful [Python Libraries](#python-extras).
For SparkR users, this script includes several helpful [R Libraries](#r-extras).
####Installing the required components to launch a virtual machine.
This script requires three applications, [Ansible](http://docs.ansible.com/ansible/intro_installation.html#latest-releases-via-pip "Ansible"), [Vagrant](http://www.vagrantup.com "Vagrant") and [Virtual Box](https://www.virtualbox.org/ "Virtual Box"). All of these applications are freely available as Open Source projects and extremely easy to set up on most operating systems.
@ -40,11 +40,11 @@ If you are running Windows and don't yet have python installed, [install Python
1. Download and Install Vagrant: [Vagrant Downloads](http://www.vagrantup.com/downloads)
2. Install Ansible: [Ansible Python pip install](http://docs.ansible.com/ansible/intro_installation.html#latest-releases-via-pip)
```
sudo easy_install pip
sudo pip install ansible
ansible --version
ansible --version
```
After then, please check whether it reports **ansible version 1.9.2 or higher**.
@ -70,7 +70,7 @@ Cloning the project again may seem counter intuitive, since this script likley o
Synced folders enable Vagrant to sync a folder on the host machine to the guest machine, allowing you to continue working on your project's files on your host machine, but use the resources in the guest machine to compile or run your project. _[(1) Synced Folder Description from Vagrant Up](https://docs.vagrantup.com/v2/synced-folders/index.html)_
By default, Vagrant will share your project directory (the directory with the Vagrantfile) to `/vagrant`. Which means you should be able to build within the guest machine after you
By default, Vagrant will share your project directory (the directory with the Vagrantfile) to `/vagrant`. Which means you should be able to build within the guest machine after you
`cd /vagrant/incubator-zeppelin`
@ -95,7 +95,7 @@ The virtual machine consists of:
- openjdk-7-jdk
- Python addons: pip, matplotlib, scipy, numpy, pandas
- [R](https://www.r-project.org/) and R Packages required to run the R Interpreter and the related R tutorial notebook, including: Knitr, devtools, repr, rCharts, ggplot2, googleVis, mplot, htmltools, base64enc, data.table
### How to build & run Zeppelin
This assumes you've already cloned the project either on the host machine in the zeppelin-dev directory (to be shared with the guest machine) or cloned directly into a directory while running inside the guest machine. The following build steps will also include Python and R support via PySpark and SparkR:
@ -138,7 +138,7 @@ import scipy
import pandas
import matplotlib
print "numpy " + numpy.__version__
print "numpy " + numpy.__version__
print "scipy " + scipy.__version__
print "pandas " + pandas.__version__
print "matplotlib " + matplotlib.__version__
@ -176,7 +176,7 @@ plt.xlabel('Performance')
plt.title('How fast do you want to go today?')
show(plt)
```
```
### R Extras

View file

@ -25,16 +25,16 @@ This page describes how to pre-configure a bare metal node, configure Zeppelin a
## Prepare Node
### Zeppelin user (Optional)
This step is optional, however its nice to run Zeppelin under its own user. In case you do not like to use Zeppelin (hope not) the user could be deleted along with all the pacakges that were installed for Zeppelin, Zeppelin binary itself and associated directories.
This step is optional, however its nice to run Zeppelin under its own user. In case you do not like to use Zeppelin (hope not) the user could be deleted along with all the packages that were installed for Zeppelin, Zeppelin binary itself and associated directories.
Create a zeppelin user and switch to zeppelin user or if zeppelin user is already created then login as zeppelin.
```bash
useradd zeppelin
su - zeppelin
su - zeppelin
whoami
```
Assuming a zeppelin user is created then running whoami command must return
Assuming a zeppelin user is created then running whoami command must return
```bash
zeppelin
@ -48,7 +48,7 @@ Its assumed in the rest of the document that zeppelin user is indeed created and
* Java 1.7
* Hadoop client
* Spark
* Internet connection is required.
* Internet connection is required.
It's assumed that the node has CentOS 6.x installed on it. Although any version of Linux distribution should work fine.
@ -83,7 +83,7 @@ This document assumes that Zeppelin is located under `/home/zeppelin/incubator-z
Zeppelin configuration needs to be modified to connect to YARN cluster. Create a copy of zeppelin environment shell script.
```bash
cp /home/zeppelin/incubator-zeppelin/conf/zeppelin-env.sh.template /home/zeppelin/incubator-zeppelin/conf/zeppelin-env.sh
cp /home/zeppelin/incubator-zeppelin/conf/zeppelin-env.sh.template /home/zeppelin/incubator-zeppelin/conf/zeppelin-env.sh
```
Set the following properties
@ -127,7 +127,7 @@ Zeppelin supports Hive interpreter and hence copy hive-site.xml that should be p
cp /etc/hive/conf/hive-site.xml /home/zeppelin/incubator-zeppelin/conf
```
Once Zeppelin server has started successfully, visit http://[zeppelin-server-host-name]:8080 with your web browser. Click on Interpreter tab next to Notebook dropdown. Look for Hive configurations and set them appropriately. By default hive.hiveserver2.url will be pointing to localhost and hive.hiveserver2.password/hive.hiveserver2.user are set to hive/hive. Set them as per Hive installation on YARN cluster.
Once Zeppelin server has started successfully, visit http://[zeppelin-server-host-name]:8080 with your web browser. Click on Interpreter tab next to Notebook dropdown. Look for Hive configurations and set them appropriately. By default hive.hiveserver2.url will be pointing to localhost and hive.hiveserver2.password/hive.hiveserver2.user are set to hive/hive. Set them as per Hive installation on YARN cluster.
Click on Save button. Once these configurations are updated, Zeppelin will prompt you to restart the interpreter. Accept the prompt and the interpreter will reload the configurations.
### Spark
@ -161,7 +161,7 @@ Click on Save button. Once these configurations are updated, Zeppelin will promp
Spark & Hive notebooks can be written with Zeppelin now. The resulting Spark & Hive jobs will run on configured YARN cluster.
## Debug
Zeppelin does not emit any kind of error messages on web interface when notebook/paragrah is run. If a paragraph fails it only displays ERROR. The reason for failure needs to be looked into log files which is present in logs directory under zeppelin installation base directory. Zeppelin creates a log file for each kind of interpreter.
Zeppelin does not emit any kind of error messages on web interface when notebook/paragraph is run. If a paragraph fails it only displays ERROR. The reason for failure needs to be looked into log files which is present in logs directory under zeppelin installation base directory. Zeppelin creates a log file for each kind of interpreter.
```bash
[zeppelin@zeppelin-3529 logs]$ pwd
@ -172,5 +172,5 @@ total 844
-rw-rw-r-- 1 zeppelin zeppelin 625050 Aug 3 16:05 zeppelin-interpreter-spark-zeppelin-zeppelin-3529.log
-rw-rw-r-- 1 zeppelin zeppelin 200394 Aug 3 21:15 zeppelin-zeppelin-zeppelin-3529.log
-rw-rw-r-- 1 zeppelin zeppelin 16162 Aug 3 14:03 zeppelin-zeppelin-zeppelin-3529.out
[zeppelin@zeppelin-3529 logs]$
[zeppelin@zeppelin-3529 logs]$
```

View file

@ -74,7 +74,7 @@ The **Alluxio** interpreter accepts the following commands.
<tr>
<td>copyFromLocal</td>
<td>copyFromLocal "source path" "remote path"</td>
<td>Copy the specified file specified by "source path" to the path specified by "remote path".
<td>Copy the specified file specified by "source path" to the path specified by "remote path".
This command will fail if "remote path" already exists.</td>
</tr>
<tr>
@ -230,4 +230,4 @@ Following steps are performed:
<center>
![Alluxio Interpreter Example](../assets/themes/zeppelin/img/docs-img/alluxio-example.png)
</center>
</center>

View file

@ -94,7 +94,7 @@ With the `search` command, you can send a search query to Elasticsearch. There a
* You can provide a JSON-formatted query, that is exactly what you provide when you use the REST API of Elasticsearch.
* See [Elasticsearch search API reference document](https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html) for more details about the content of the search queries.
* You can also provide the content of a `query_string`.
* This is a shortcut to a query like that: `{ "query": { "query_string": { "query": "__HERE YOUR QUERY__", "analyze_wildcard": true } } }`
* This is a shortcut to a query like that: `{ "query": { "query_string": { "query": "__HERE YOUR QUERY__", "analyze_wildcard": true } } }`
* See [Elasticsearch query string syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) for more details about the content of such a query.
```bash
@ -119,10 +119,10 @@ Examples:
```bash
%elasticsearch
search / { "query": { "match_all": { } } }
%elasticsearch
search /logs { "query": { "query_string": { "query": "request.method:GET AND status:200" } } }
%elasticsearch
search /logs { "aggs": {
"content_length_stats": {
@ -130,7 +130,7 @@ Examples:
"field": "content_length"
}
}
} }
} }
```
* With query_string elements:
@ -138,7 +138,7 @@ Examples:
```bash
%elasticsearch
search /logs request.method:GET AND status:200
%elasticsearch
search /logs (404 AND (POST OR DELETE))
```
@ -178,6 +178,9 @@ Examples:
* With a JSON query:
![Elasticsearch - Search with query](../assets/themes/zeppelin/img/docs-img/elasticsearch-search-json-query-table.png)
* With a JSON query containing a `fields` parameter (for filtering the fields in the response): in this case, all the fields values in the response are arrays, so, after flattening the result, the format of all the field names is `field_name[x]`
![Elasticsearch - Search with query and a fields param](../assets/themes/zeppelin/img/docs-img/elasticsearch-query-with-fields-param.png)
* With a query string:
![Elasticsearch - Search with query string](../assets/themes/zeppelin/img/docs-img/elasticsearch-query-string.png)

View file

@ -44,7 +44,7 @@ It supports the basic shell file commands applied to HDFS, it currently only sup
> **Tip :** Use ( Ctrl + . ) for autocompletion.
### Create Interpreter
### Create Interpreter
In a notebook, to enable the **HDFS** interpreter, click the **Gear** icon and select **HDFS**.
@ -53,4 +53,4 @@ In a notebook, to enable the **HDFS** interpreter, click the **Gear** icon and s
You can confirm that you're able to access the WebHDFS API by running a curl command against the WebHDFS end point provided to the interpreter.
Here is an example:
$> curl "http://localhost:50070/webhdfs/v1/?op=LISTSTATUS"
$> curl "http://localhost:50070/webhdfs/v1/?op=LISTSTATUS"

View file

@ -44,7 +44,7 @@ The [Apache Hive](https://hive.apache.org/) ™ data warehouse software facilita
<tr>
<td>${prefix}.driver</td>
<td></td>
<td>Driver class path of <code>%hive(${prefix})</code> </td>
<td>Driver class path of <code>%hive(${prefix})</code> </td>
</tr>
<tr>
<td>${prefix}.url</td>
@ -93,9 +93,9 @@ You can leverage [Zeppelin Dynamic Form]({{BASE_PATH}}/manual/dynamicform.html)
```sql
%hive
SELECT ${group_by}, count(*) as count
FROM retail_demo.order_lineitems_pxf
GROUP BY ${group_by=product_id,product_id|product_name|customer_id|store_id}
ORDER BY count ${order=DESC,DESC|ASC}
SELECT ${group_by}, count(*) as count
FROM retail_demo.order_lineitems_pxf
GROUP BY ${group_by=product_id,product_id|product_name|customer_id|store_id}
ORDER BY count ${order=DESC,DESC|ASC}
LIMIT ${limit=10};
```

View file

@ -18,17 +18,17 @@ You can use Zeppelin to retrieve distributed data from cache using Ignite SQL in
### Installing and Running Ignite example
In order to use Ignite interpreters, you may install Apache Ignite in some simple steps:
1. Download Ignite [source release](https://ignite.apache.org/download.html#sources) or [binary release](https://ignite.apache.org/download.html#binaries) whatever you want. But you must download Ignite as the same version of Zeppelin's. If it is not, you can't use scala code on Zeppelin. You can find ignite version in Zepplin at the pom.xml which is placed under `path/to/your-Zeppelin/ignite/pom.xml` ( Of course, in Zeppelin source release ). Please check `ignite.version` .<br>Currently, Zeppelin provides ignite only in Zeppelin source release. So, if you download Zeppelin binary release( `zeppelin-0.5.0-incubating-bin-spark-xxx-hadoop-xx` ), you can not use ignite interpreter on Zeppelin. We are planning to include ignite in a future binary release.
2. Examples are shipped as a separate Maven project, so to start running you simply need to import provided <dest_dir>/apache-ignite-fabric-1.2.0-incubating-bin/pom.xml file into your favourite IDE, such as Eclipse.
1. Download Ignite [source release](https://ignite.apache.org/download.html#sources) or [binary release](https://ignite.apache.org/download.html#binaries) whatever you want. But you must download Ignite as the same version of Zeppelin's. If it is not, you can't use scala code on Zeppelin. You can find ignite version in Zeppelin at the pom.xml which is placed under `path/to/your-Zeppelin/ignite/pom.xml` ( Of course, in Zeppelin source release ). Please check `ignite.version` .<br>Currently, Zeppelin provides ignite only in Zeppelin source release. So, if you download Zeppelin binary release( `zeppelin-0.5.0-incubating-bin-spark-xxx-hadoop-xx` ), you can not use ignite interpreter on Zeppelin. We are planning to include ignite in a future binary release.
2. Examples are shipped as a separate Maven project, so to start running you simply need to import provided <dest_dir>/apache-ignite-fabric-1.2.0-incubating-bin/pom.xml file into your favourite IDE, such as Eclipse.
* In case of Eclipse, Eclipse -> File -> Import -> Existing Maven Projects
* Set examples directory path to Eclipse and select the pom.xml.
* Then start `org.apache.ignite.examples.ExampleNodeStartup` (or whatever you want) to run at least one or more ignite node. When you run example code, you may notice that the number of node is increase one by one.
* Then start `org.apache.ignite.examples.ExampleNodeStartup` (or whatever you want) to run at least one or more ignite node. When you run example code, you may notice that the number of node is increase one by one.
> **Tip. If you want to run Ignite examples on the cli not IDE, you can export executable Jar file from IDE. Then run it by using below command.**
```
$ nohup java -jar </path/to/your Jar file name>
```
$ nohup java -jar </path/to/your Jar file name>
```
### Configuring Ignite Interpreter
@ -78,17 +78,17 @@ For more interpreter binding information see [here](http://zeppelin.incubator.ap
### How to use Ignite SQL interpreter
In order to execute SQL query, use ` %ignite.ignitesql ` prefix. <br>
Supposing you are running `org.apache.ignite.examples.streaming.wordcount.StreamWords`, then you can use "words" cache( Of course you have to specify this cache name to the Ignite interpreter setting section `ignite.jdbc.url` of Zeppelin ).
Supposing you are running `org.apache.ignite.examples.streaming.wordcount.StreamWords`, then you can use "words" cache( Of course you have to specify this cache name to the Ignite interpreter setting section `ignite.jdbc.url` of Zeppelin ).
For example, you can select top 10 words in the words cache using the following query
```
%ignite.ignitesql
select _val, count(_val) as cnt from String group by _val order by cnt desc limit 10
```
```
%ignite.ignitesql
select _val, count(_val) as cnt from String group by _val order by cnt desc limit 10
```
![IgniteSql on Zeppelin](../assets/themes/zeppelin/img/docs-img/ignite-sql-example.png)
As long as your Ignite version and Zeppelin Ignite version is same, you can also use scala code. Please check the Zeppelin Ignite version before you download your own Ignite.
As long as your Ignite version and Zeppelin Ignite version is same, you can also use scala code. Please check the Zeppelin Ignite version before you download your own Ignite.
```
%ignite

View file

@ -17,7 +17,7 @@ group: manual
In order to use Lens interpreters, you may install Apache Lens in some simple steps:
1. Download Lens for latest version from [the ASF](http://www.apache.org/dyn/closer.lua/lens/2.3-beta). Or the older release can be found [in the Archives](http://archive.apache.org/dist/lens/).
2. Before running Lens, you have to set HIVE_HOME and HADOOP_HOME. If you want to get more information about this, please refer to [here](http://lens.apache.org/lenshome/install-and-run.html#Installation). Lens also provides Pseudo Distributed mode. [Lens pseudo-distributed setup](http://lens.apache.org/lenshome/pseudo-distributed-setup.html) is done by using [docker](https://www.docker.com/). Hive server and hadoop daemons are run as separate processes in lens pseudo-distributed setup.
2. Before running Lens, you have to set HIVE_HOME and HADOOP_HOME. If you want to get more information about this, please refer to [here](http://lens.apache.org/lenshome/install-and-run.html#Installation). Lens also provides Pseudo Distributed mode. [Lens pseudo-distributed setup](http://lens.apache.org/lenshome/pseudo-distributed-setup.html) is done by using [docker](https://www.docker.com/). Hive server and hadoop daemons are run as separate processes in lens pseudo-distributed setup.
3. Now, you can start lens server (or stop).
```
@ -77,16 +77,16 @@ At the "Interpreters" menu, you can edit Lens interpreter or create new one. Zep
![Apache Lens Interpreter Setting](../assets/themes/zeppelin/img/docs-img/lens-interpreter-setting.png)
### Interpreter Bindging for Zeppelin Notebook
### Interpreter Binding for Zeppelin Notebook
After configuring Lens interpreter, create your own notebook, then you can bind interpreters like below image.
![Zeppelin Notebook Interpreter Biding](../assets/themes/zeppelin/img/docs-img/lens-interpreter-binding.png)
![Zeppelin Notebook Interpreter Binding](../assets/themes/zeppelin/img/docs-img/lens-interpreter-binding.png)
For more interpreter binding information see [here](http://zeppelin.incubator.apache.org/docs/manual/interpreters.html).
### How to use
You can analyze your data by using [OLAP Cube](http://lens.apache.org/user/olap-cube.html) [QL](http://lens.apache.org/user/cli.html) which is a high level SQL like language to query and describe data sets organized in data cubes.
You may experience OLAP Cube like this [Video tutorial](https://cwiki.apache.org/confluence/display/LENS/2015/07/13/20+Minute+video+demo+of+Apache+Lens+through+examples).
### How to use
You can analyze your data by using [OLAP Cube](http://lens.apache.org/user/olap-cube.html) [QL](http://lens.apache.org/user/cli.html) which is a high level SQL like language to query and describe data sets organized in data cubes.
You may experience OLAP Cube like this [Video tutorial](https://cwiki.apache.org/confluence/display/LENS/2015/07/13/20+Minute+video+demo+of+Apache+Lens+through+examples).
As you can see in this video, they are using Lens Client Shell(./bin/lens-cli.sh). All of these functions also can be used on Zeppelin by using Lens interpreter.
<li> Create and Use(Switch) Databases.
@ -105,7 +105,7 @@ use newDb
create storage your/path/to/lens/client/examples/resources/db-storage.xml
```
<li> Create Dimensions, Show fields and join-chains of them.
<li> Create Dimensions, Show fields and join-chains of them.
```
create dimension your/path/to/lens/client/examples/resources/customer.xml
@ -121,8 +121,8 @@ dimension show joinchains customer
<li> Create Caches, Show fields and join-chains of them.
```
create cube your/path/to/lens/client/examples/resources/sales-cube.xml
```
create cube your/path/to/lens/client/examples/resources/sales-cube.xml
```
```
@ -133,7 +133,7 @@ cube show fields sales
cube show joinchains sales
```
<li> Create Dimtables and Fact.
<li> Create Dimtables and Fact.
```
create dimtable your/path/to/lens/client/examples/resources/customer_table.xml
@ -163,7 +163,7 @@ query execute cube select customer_city_name, product_details.description, produ
These are just examples that provided in advance by Lens. If you want to explore whole tutorials of Lens, see the [tutorial video](https://cwiki.apache.org/confluence/display/LENS/2015/07/13/20+Minute+video+demo+of+Apache+Lens+through+examples).
### Lens UI Service
### Lens UI Service
Lens also provides web UI service. Once the server starts up, you can open the service on http://serverhost:19999/index.html and browse. You may also check the structure that you made and use query easily here.
![Lens UI Servive](../assets/themes/zeppelin/img/docs-img/lens-ui-service.png)
![Lens UI Service](../assets/themes/zeppelin/img/docs-img/lens-ui-service.png)

View file

@ -15,26 +15,26 @@ This is a the Apache (incubating) Zeppelin project, with the addition of support
Additional requirements for the R interpreter are:
* R 3.1 or later (earlier versions may work, but have not been tested)
* The `evaluate` R package.
* The `evaluate` R package.
For full R support, you will also need the following R packages:
* `knitr`
* `knitr`
* `repr` -- available with `devtools::install_github("IRkernel/repr")`
* `htmltools` -- required for some interactive plotting
* `base64enc` -- required to view R base plots
### Configuration
### Configuration
To run Zeppelin with the R Interpreter, the SPARK_HOME environment variable must be set. The best way to do this is by editing `conf/zeppelin-env.sh`.
To run Zeppelin with the R Interpreter, the SPARK_HOME environment variable must be set. The best way to do this is by editing `conf/zeppelin-env.sh`.
If it is not set, the R Interpreter will not be able to interface with Spark.
If it is not set, the R Interpreter will not be able to interface with Spark.
You should also copy `conf/zeppelin-site.xml.template` to `conf/zeppelin-site.xml`. That will ensure that Zeppelin sees the R Interpreter the first time it starts up.
You should also copy `conf/zeppelin-site.xml.template` to `conf/zeppelin-site.xml`. That will ensure that Zeppelin sees the R Interpreter the first time it starts up.
### Using the R Interpreter
By default, the R Interpreter appears as two Zeppelin Interpreters, `%r` and `%knitr`.
By default, the R Interpreter appears as two Zeppelin Interpreters, `%r` and `%knitr`.
`%r` will behave like an ordinary REPL. You can execute commands as in the CLI.
@ -44,7 +44,7 @@ R base plotting is fully supported
[![replhist](screenshots/replhist.png)](screenshots/replhist.png)
If you return a data.frame, Zeppelin will attempt to display it using Zeppelin's built-in visualizations.
If you return a data.frame, Zeppelin will attempt to display it using Zeppelin's built-in visualizations.
[![replhist](screenshots/replhead.png)](screenshots/replhead.png)
@ -61,7 +61,7 @@ The two interpreters share the same environment. If you define a variable from
If `SPARK_HOME` is set, the `SparkR` package will be loaded automatically:
[![sparkrfaithful](screenshots/sparkrfaithful.png)](screenshots/sparkrfaithful.png)
The Spark Context and SQL Context are created and injected into the local environment automatically as `sc` and `sql`.
The same context are shared with the `%spark`, `%sql` and `%pyspark` interpreters:
@ -79,9 +79,9 @@ And vice versa:
### Caveats & Troubleshooting
* Almost all issues with the R interpreter turned out to be caused by an incorrectly set `SPARK_HOME`. The R interpreter must load a version of the `SparkR` package that matches the running version of Spark, and it does this by searching `SPARK_HOME`. If Zeppelin isn't configured to interface with Spark in `SPARK_HOME`, the R interpreter will not be able to connect to Spark.
* Almost all issues with the R interpreter turned out to be caused by an incorrectly set `SPARK_HOME`. The R interpreter must load a version of the `SparkR` package that matches the running version of Spark, and it does this by searching `SPARK_HOME`. If Zeppelin isn't configured to interface with Spark in `SPARK_HOME`, the R interpreter will not be able to connect to Spark.
* The `knitr` environment is persistent. If you run a chunk from Zeppelin that changes a variable, then run the same chunk again, the variable has already been changed. Use immutable variables.
* The `knitr` environment is persistent. If you run a chunk from Zeppelin that changes a variable, then run the same chunk again, the variable has already been changed. Use immutable variables.
* (Note that `%spark.r` and `$r` are two different ways of calling the same interpreter, as are `%spark.knitr` and `%knitr`. By default, Zeppelin puts the R interpreters in the `%spark.` Interpreter Group.
@ -89,13 +89,13 @@ And vice versa:
* If you return a data.frame (for instance, from calling `head()`) from the `%spark.r` interpreter, it will be parsed by Zeppelin's built-in data visualization system.
* Why `knitr` Instead of `rmarkdown`? Why no `htmlwidgets`? In order to support `htmlwidgets`, which has indirect dependencies, `rmarkdown` uses `pandoc`, which requires writing to and reading from disc. This makes it many times slower than `knitr`, which can operate entirely in RAM.
* Why `knitr` Instead of `rmarkdown`? Why no `htmlwidgets`? In order to support `htmlwidgets`, which has indirect dependencies, `rmarkdown` uses `pandoc`, which requires writing to and reading from disc. This makes it many times slower than `knitr`, which can operate entirely in RAM.
* Why no `ggvis` or `shiny`? Supporting `shiny` would require integrating a reverse-proxy into Zeppelin, which is a task.
* Why no `ggvis` or `shiny`? Supporting `shiny` would require integrating a reverse-proxy into Zeppelin, which is a task.
* Max OS X & case-insensitive filesystem. If you try to install on a case-insensitive filesystem, which is the Mac OS X default, maven can unintentionally delete the install directory because `r` and `R` become the same subdirectory.
* Max OS X & case-insensitive filesystem. If you try to install on a case-insensitive filesystem, which is the Mac OS X default, maven can unintentionally delete the install directory because `r` and `R` become the same subdirectory.
* Error `unable to start device X11` with the repl interpreter. Check your shell login scripts to see if they are adjusting the `DISPLAY` environment variable. This is common on some operating systems as a workaround for ssh issues, but can interfere with R plotting.
* Error `unable to start device X11` with the repl interpreter. Check your shell login scripts to see if they are adjusting the `DISPLAY` environment variable. This is common on some operating systems as a workaround for ssh issues, but can interfere with R plotting.
* akka Library Version or `TTransport` errors. This can happen if you try to run Zeppelin with a SPARK_HOME that has a version of Spark other than the one specified with `-Pspark-1.x` when Zeppelin was compiled.
@ -111,7 +111,7 @@ To run R code and visualize plots in Apache Zeppelin, you will need R on your ma
+ For Centos: `yum install R R-devel libcurl-devel openssl-devel`
+ For Ubuntu: `apt-get install r-base`
Validate your installation with a simple R command:
```
@ -135,4 +135,3 @@ We recommend you to also install the following optional R libraries for happy da
+ caret
+ sqldf
+ wordcloud

View file

@ -72,6 +72,6 @@ If you click on the icon for the pie chart, you should be able to see a chart li
![Scalding - Pie - Chart](../assets/themes/zeppelin/img/docs-img/scalding-pie.png)
### Current Status & Future Work
The current implementation of the Scalding interpreter does not support canceling jobs, or fine-grained progress updates.
The current implementation of the Scalding interpreter does not support canceling jobs, or fine-grained progress updates.
The pre-configured Scalding interpreter only supports Scalding in local mode. Hadoop mode for Scalding is currently unsupported, and will be future work (contributions welcome!).

View file

@ -8,7 +8,7 @@ group: manual
## Spark Interpreter for Apache Zeppelin
[Apache Spark](http://spark.apache.org) is supported in Zeppelin with
[Apache Spark](http://spark.apache.org) is supported in Zeppelin with
Spark Interpreter group, which consisted of 4 interpreters.
<table class="table-configuration">
@ -20,17 +20,22 @@ Spark Interpreter group, which consisted of 4 interpreters.
<tr>
<td>%spark</td>
<td>SparkInterpreter</td>
<td>Creates SparkContext and provides scala environment</td>
<td>Creates a SparkContext and provides a scala environment</td>
</tr>
<tr>
<td>%pyspark</td>
<td>PySparkInterpreter</td>
<td>Provides python environment</td>
<td>Provides a python environment</td>
</tr>
<tr>
<td>%r</td>
<td>SparkRInterpreter</td>
<td>Provides an R environment with SparkR support</td>
</tr>
<tr>
<td>%sql</td>
<td>SparkSQLInterpreter</td>
<td>Provides SQL environment</td>
<td>Provides a SQL environment</td>
</tr>
<tr>
<td>%dep</td>
@ -40,8 +45,8 @@ Spark Interpreter group, which consisted of 4 interpreters.
</table>
## Configuration
Zeppelin provides the below properties for Spark interpreter.
You can also set other Spark properties which are not listed in the table. If so, please refer to [Spark Available Properties](http://spark.apache.org/docs/latest/configuration.html#available-properties).
The Spark interpreter can be configured with properties provided by Zeppelin.
You can also set other Spark properties which are not listed in the table. For a list of additional properties, refer to [Spark Available Properties](http://spark.apache.org/docs/latest/configuration.html#available-properties).
<table class="table-configuration">
<tr>
<th>Property</th>
@ -273,13 +278,13 @@ z.put("objName", myObject)
%pyspark
myObject = z.get("objName")
{% endhighlight %}
</div>
</div>
### Form Creation
ZeppelinContext provides functions for creating forms.
ZeppelinContext provides functions for creating forms.
In scala and python environments, you can create forms programmatically.
<div class="codetabs">
<div data-lang="scala" markdown="1">
@ -306,13 +311,13 @@ z.select("formName", "option1", Seq(("option1", "option1DisplayName"),
{% highlight python %}
%pyspark
# Create text input form
# Create text input form
z.input("formName")
# Create text input form with default value
# Create text input form with default value
z.input("formName", "defaultValue")
# Create select form
# Create select form
z.select("formName", [("option1", "option1DisplayName"),
("option2", "option2DisplayName")])
@ -320,7 +325,7 @@ z.select("formName", [("option1", "option1DisplayName"),
z.select("formName", [("option1", "option1DisplayName"),
("option2", "option2DisplayName")], "option1")
{% endhighlight %}
</div>
</div>
@ -334,9 +339,10 @@ select * from ${table=defaultTableName} where text like '%${search}%'
To learn more about dynamic form, checkout [Dynamic Form](../manual/dynamicform.html).
### Separate Interpreter for each note
### Interpreter setting option.
Interpreter setting can choose one of 'shared', 'scoped', 'isolated' option. Spark interpreter creates separate scala compiler per each notebook but share a single SparkContext in 'scoped' mode (experimental). It creates separate SparkContext per each notebook in 'isolated' mode.
In 'Separate Interpreter for each note' mode, SparkInterpreter creates scala compiler per each notebook. However it still shares the single SparkContext.
## Setting up Zeppelin with Kerberos
Logical setup with Zeppelin, Kerberos Key Distribution Center (KDC), and Spark on YARN:
@ -359,5 +365,3 @@ This is to make the server communicate with KDC.
> **NOTE:** If you do not have access to the above spark-defaults.conf file, optionally, you may add the lines to the Spark Interpreter through the Interpreter tab in the Zeppelin UI.
4. That's it. Play with Zeppelin !

View file

@ -1,6 +1,6 @@
---
layout: page
title: "Dependnecy Management"
title: "Dependency Management"
description: ""
group: manual
---
@ -71,4 +71,3 @@ When your code requires external library, instead of doing download/copy/restart
</ol>
</div>
</div>

View file

@ -32,13 +32,13 @@ When you click the ```+Create``` button in the interpreter page, the interpreter
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_create.png">
## What is Zeppelin Interpreter Setting?
Zeppelin interpreter setting is the configuration of a given interpreter on Zeppelin server. For example, the properties are required for hive JDBC interpreter to connect to the Hive server.
Zeppelin interpreter setting is the configuration of a given interpreter on Zeppelin server. For example, the properties are required for hive JDBC interpreter to connect to the Hive server.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_setting.png">
Properties are exported as environment variable when property name is consisted of upper characters, numbers and underscore ([A-Z_0-9]). Otherwise set properties as JVM property.
Each notebook can be binded to multiple Interpreter Settings using setting icon on upper right corner of the notebook.
Each notebook can be bound to multiple Interpreter Settings using setting icon on upper right corner of the notebook.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_binding.png" width="800px">
@ -56,8 +56,7 @@ Each interpreters is belonged to a single group and registered together. All of
## Interpreter binding mode
Each Interpreter Setting can choose one of two different interpreter binding mode.
Shared mode (default) and 'Separate Interpreter for each note' mode. In shared mode, every notebook binded to the Interpreter Setting will share the single Interpreter instance. In 'Separate Interpreter for each note' mode, each notebook will create new Interpreter instance. Therefore each notebook will have fresh new Interpreter environment.
Each Interpreter Setting can choose one of 'shared', 'scoped', 'isolated' interpreter binding mode.
In 'shared' mode, every notebook bound to the Interpreter Setting will share the single Interpreter instance. In 'scoped' mode, each notebook will create new Interpreter instance in the same interpreter process. In 'isolated' mode, each notebook will create new Interpreter process.
<img src="/assets/themes/zeppelin/img/screenshots/interpreter_persession.png" width="400px">

View file

@ -21,66 +21,66 @@ limitations under the License.
## Customize your zeppelin homepage
Zeppelin allows you to use one of the notebooks you create as your zeppelin Homepage.
With that you can brand your zeppelin installation,
With that you can brand your zeppelin installation,
adjust the instruction to your users needs and even translate to other languages.
<br />
### How to set a notebook as your zeppelin homepage
The process for creating your homepage is very simple as shown below:
1. Create a notebook using zeppelin
2. Set the notebook id in the config file
3. Restart zeppelin
<br />
#### Create a notebook using zeppelin
Create a new notebook using zeppelin,
you can use ```%md``` interpreter for markdown content or any other interpreter you like.
You can also use the display system to generate [text](../displaysystem/display.html),
You can also use the display system to generate [text](../displaysystem/display.html),
[html](../displaysystem/display.html#html),[table](../displaysystem/table.html) or
[angular](../displaysystem/angular.html)
Run (shift+Enter) the notebook and see the output. Optionally, change the notebook view to report to hide
Run (shift+Enter) the notebook and see the output. Optionally, change the notebook view to report to hide
the code sections.
<br />
#### Set the notebook id in the config file
To set the notebook id in the config file you should copy it from the last word in the notebook url
To set the notebook id in the config file you should copy it from the last word in the notebook url
for example
<img src="/assets/themes/zeppelin/img/screenshots/homepage_notebook_id.png" />
Set the notebook id to the ```ZEPPELIN_NOTEBOOK_HOMESCREEN``` environment variable
or ```zeppelin.notebook.homescreen``` property.
You can also set the ```ZEPPELIN_NOTEBOOK_HOMESCREEN_HIDE``` environment variable
Set the notebook id to the ```ZEPPELIN_NOTEBOOK_HOMESCREEN``` environment variable
or ```zeppelin.notebook.homescreen``` property.
You can also set the ```ZEPPELIN_NOTEBOOK_HOMESCREEN_HIDE``` environment variable
or ```zeppelin.notebook.homescreen.hide``` property to hide the new notebook from the notebook list.
<br />
#### Restart zeppelin
Restart your zeppelin server
```
./bin/zeppelin-deamon stop
./bin/zeppelin-deamon stop
./bin/zeppelin-deamon start
```
####That's it! Open your browser and navigate to zeppelin and see your customized homepage...
<br />
### Show notebooks list in your custom homepage
If you want to display the list of notebooks on your custom zeppelin homepage all
If you want to display the list of notebooks on your custom zeppelin homepage all
you need to do is use our %angular support.
<br />
Add the following code to a paragraph in you home page and run it... walla! you have your notebooks list.
```javascript
println(
"""%angular
"""%angular
<div class="col-md-4" ng-controller="HomeCtrl as home">
<h4>Notebooks</h4>
<div>
@ -95,15 +95,15 @@ you need to do is use our %angular support.
</div>
""")
```
After running the notebook you will see output similar to this one:
<img src="/assets/themes/zeppelin/img/screenshots/homepage_notebook_list.png" />
The main trick here relays in linking the ```<div>``` to the controller:
```javascript
<div class="col-md-4" ng-controller="HomeCtrl as home">
```
Once we have ```home``` as our controller variable in our ```<div></div>```
we can use ```home.notes.list``` to get access to the notebook list.
we can use ```home.notes.list``` to get access to the notebook list.

View file

@ -20,7 +20,7 @@ limitations under the License.
{% include JB/setup %}
# Shiro authentication for Apache Zeppelin
[Apache Shiro](http://shiro.apache.org/) is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. In this documentation, we will explain step by step how Shiro works for Zeppelin notebook authentication.
[Apache Shiro](http://shiro.apache.org/) is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. In this documentation, we will explain step by step how Shiro works for Zeppelin notebook authentication.
When you connect to Apache Zeppelin, you will be asked to enter your credentials. Once you logged in, then you have access to all notes including other user's notes.
@ -28,7 +28,7 @@ When you connect to Apache Zeppelin, you will be asked to enter your credentials
You can setup **Zeppelin notebook authentication** in some simple steps.
####1. Secure the HTTP channel
To secure the HTTP channel, you have to change both **anon** and **authcBasic** settings in `conf/shiro.ini`. In here, **anon** means "the access is anonymous" and **authcBasic** means "basic auth security".
To secure the HTTP channel, you have to change both **anon** and **authcBasic** settings in `conf/shiro.ini`. In here, **anon** means "the access is anonymous" and **authcBasic** means "basic auth security".
The default status of them is
@ -36,7 +36,7 @@ The default status of them is
/** = anon
#/** = authcBasic
```
Deactivate the line "/** = anon" and activate the line "/** = authcBasic" in `conf/shiro.ini` file.
Deactivate the line "/** = anon" and activate the line "/** = authcBasic" in `conf/shiro.ini` file.
```
#/** = anon
@ -49,15 +49,15 @@ For the further information about `shiro.ini` file format, please refer to [Shi
Set to property **zeppelin.anonymous.allowed** to **false** in `conf/zeppelin-site.xml`. If you don't have this file yet, just copy `conf/zeppelin-site.xml.template` to `conf/zeppelin-site.xml`.
####3. Start Zeppelin
```
bin/zeppelin-daemon.sh start (or restart)
```
Then you can browse Zeppelin at [http://localhost:8080](http://localhost:8080).
####4. Login
Finally, you can login using one of the below **username/password** combinations.
Finally, you can login using one of the below **username/password** combinations.
<center><img src="../assets/themes/zeppelin/img/docs-img/zeppelin-login.png" width="40%" height="40%"></center>
@ -66,7 +66,7 @@ admin = password1
user1 = password2
user2 = password3
```
Those combinations are defined in the `conf/shiro.ini` file.
> **NOTE :** This documentation is originally from [SECURITY-README.md](https://github.com/apache/incubator-zeppelin/blob/master/SECURITY-README.md).
> **NOTE :** This documentation is originally from [SECURITY-README.md](https://github.com/apache/incubator-zeppelin/blob/master/SECURITY-README.md).

View file

@ -21,18 +21,18 @@ limitations under the License.
## Zeppelin REST API
Zeppelin provides several REST API's for interaction and remote activation of zeppelin functionality.
All REST API are available starting with the following endpoint ```http://[zeppelin-server]:[zeppelin-port]/api```
Note that zeppein REST API receive or return JSON objects, it it recommended you install some JSON viewers such as
Note that Zeppelin REST API receive or return JSON objects, it it recommended you install some JSON viewers such as
[JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc)
If you work with zeppelin and find a need for an additional REST API please [file an issue or send us mail](../../community.html)
If you work with zeppelin and find a need for an additional REST API please [file an issue or send us mail](../../community.html)
<br />
### Configuration REST API list
<table class="table-configuration">
<col width="200">
<tr>
@ -41,7 +41,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method return all key/value pair of configurations on the server.<br/>
<td>This ```GET``` method return all key/value pair of configurations on the server.<br/>
Note: For security reason, some pairs would not be shown.</td>
</tr>
<tr>
@ -94,9 +94,9 @@ limitations under the License.
</td>
</tr>
</table>
<br/>
<table class="table-configuration">
<col width="200">
<tr>
@ -105,7 +105,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method return all prefix matched key/value pair of configurations on the server.<br/>
<td>This ```GET``` method return all prefix matched key/value pair of configurations on the server.<br/>
Note: For security reason, some pairs would not be shown.</td>
</tr>
<tr>

View file

@ -21,18 +21,18 @@ limitations under the License.
## Zeppelin REST API
Zeppelin provides several REST API's for interaction and remote activation of zeppelin functionality.
All REST API are available starting with the following endpoint `http://[zeppelin-server]:[zeppelin-port]/api`.
Note that zeppein REST API receive or return JSON objects, it it recommended you install some JSON viewers such as
Note that Zeppelin REST API receive or return JSON objects, it it recommended you install some JSON viewers such as
[JSON View](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc).
If you work with zeppelin and find a need for an additional REST API, please [file an issue or send us mail](http://zeppelin.incubator.apache.org/community.html).
If you work with zeppelin and find a need for an additional REST API, please [file an issue or send us mail](http://zeppelin.incubator.apache.org/community.html).
<br />
## Interpreter REST API List
The role of registered interpreters, settings and interpreters group are described in [here](../manual/interpreters.html).
### 1. List of Registered Interpreters & Interpreter Settings
<table class="table-configuration">
@ -106,9 +106,9 @@ limitations under the License.
</td>
</tr>
</table>
<br/>
<table class="table-configuration">
<col width="200">
<tr>
@ -268,8 +268,8 @@ limitations under the License.
</td>
</tr>
</table>
<br/>
### 3. Update an Interpreter Setting
@ -354,7 +354,7 @@ limitations under the License.
</tr>
</table>
<br/>
### 4. Delete an Interpreter Setting
@ -388,9 +388,9 @@ limitations under the License.
</tr>
</table>
<br/>
### 5. Restart an Interpreter
### 5. Restart an Interpreter
<table class="table-configuration">
<col width="200">

View file

@ -21,20 +21,20 @@ limitations under the License.
## Zeppelin REST API
Zeppelin provides several REST APIs for interaction and remote activation of zeppelin functionality.
All REST APIs are available starting with the following endpoint ```http://[zeppelin-server]:[zeppelin-port]/api```
Note that zeppelin REST APIs receive or return JSON objects, it is recommended for you to install some JSON viewers
such as [JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc)
If you work with zeppelin and find a need for an additional REST API please [file an issue or send us mail](../../community.html)
If you work with zeppelin and find a need for an additional REST API please [file an issue or send us mail](../../community.html)
<br />
### Notebook REST API list
Notebooks REST API supports the following operations: List, Create, Get, Delete, Clone, Run, Export, Import as detailed in the following table
Notebooks REST API supports the following operations: List, Create, Get, Delete, Clone, Run, Export, Import as detailed in the following table
<table class="table-configuration">
<col width="200">
<tr>
@ -64,7 +64,7 @@ limitations under the License.
<td><pre>{"status":"OK","message":"","body":[{"name":"Homepage","id":"2AV4WUEMK"},{"name":"Zeppelin Tutorial","id":"2A94M5J1Z"}]}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -99,7 +99,7 @@ limitations under the License.
<td> sample JSON input (with initial paragraphs) </td>
<td><pre>
{
"name": "name of new notebook",
"name": "name of new notebook",
"paragraphs": [
{
"title": "paragraph title1",
@ -118,7 +118,7 @@ limitations under the License.
<td><pre>{"status": "CREATED","message": "","body": "2AZPHY918"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -220,7 +220,7 @@ limitations under the License.
</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -251,9 +251,9 @@ limitations under the License.
<td><pre>{"status":"OK","message":""}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
<col width="200">
<tr>
@ -262,7 +262,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method clones a notebook by the given id and create a new notebook using the given name
<td>This ```POST``` method clones a notebook by the given id and create a new notebook using the given name
or default name if none given.
The body field of the returned JSON contains the new notebook id.
</td>
@ -288,7 +288,7 @@ limitations under the License.
<td><pre>{"status": "CREATED","message": "","body": "2AZPHY918"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -319,7 +319,7 @@ limitations under the License.
<td><pre>{"status":"OK"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -330,7 +330,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method stops all paragraph in the given notebook id.
<td>This ```DELETE``` method stops all paragraph in the given notebook id.
</td>
</tr>
<tr>
@ -350,9 +350,9 @@ limitations under the License.
<td><pre>{"status":"OK"}</pre></td>
</tr>
</table>
<br/>
<br/>
<table class="table-configuration">
@ -363,7 +363,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method gets all paragraph status by the given notebook id.
<td>This ```GET``` method gets all paragraph status by the given notebook id.
The body field of the returned JSON contains of the array that compose of the paragraph id, paragraph status, paragraph finish date, paragraph started date.
</td>
</tr>
@ -384,7 +384,7 @@ limitations under the License.
<td><pre>{"status":"OK","body":[{"id":"20151121-212654_766735423","status":"FINISHED","finished":"Tue Nov 24 14:21:40 KST 2015","started":"Tue Nov 24 14:21:39 KST 2015"},{"progress":"1","id":"20151121-212657_730976687","status":"RUNNING","finished":"Tue Nov 24 14:21:35 KST 2015","started":"Tue Nov 24 14:21:40 KST 2015"}]}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -395,7 +395,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method runs the paragraph by given notebook and paragraph id.
<td>This ```POST``` method runs the paragraph by given notebook and paragraph id.
</td>
</tr>
<tr>
@ -427,7 +427,7 @@ limitations under the License.
<td><pre>{"status":"OK"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -438,7 +438,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method stops the paragraph by given notebook and paragraph id.
<td>This ```DELETE``` method stops the paragraph by given notebook and paragraph id.
</td>
</tr>
<tr>
@ -458,7 +458,7 @@ limitations under the License.
<td><pre>{"status":"OK"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -469,7 +469,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method adds cron job by the given notebook id.
<td>This ```POST``` method adds cron job by the given notebook id.
</td>
</tr>
<tr>
@ -493,7 +493,7 @@ limitations under the License.
<td><pre>{"status":"OK"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -504,7 +504,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method removes cron job by the given notebook id.
<td>This ```DELETE``` method removes cron job by the given notebook id.
</td>
</tr>
<tr>
@ -524,7 +524,7 @@ limitations under the License.
<td><pre>{"status":"OK"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -535,7 +535,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method gets cron job expression of given notebook id.
<td>This ```GET``` method gets cron job expression of given notebook id.
The body field of the returned JSON contains the cron expression.
</td>
</tr>
@ -585,7 +585,7 @@ limitations under the License.
<td><pre>{"status":"OK", body: [{"id":"<noteId>/paragraph/<paragraphId>", "name":"Notebook Name", "snippet":"", "text":""}]}</pre></td>
</tr>
</table>
<br/>
@ -616,16 +616,16 @@ limitations under the License.
<tr>
<td> sample JSON input (add to the last) </td>
<td><pre>
{
"title": "Paragraph insert revised",
"text": "%spark\nprintln(\"Paragraph insert revised\")"
{
"title": "Paragraph insert revised",
"text": "%spark\nprintln(\"Paragraph insert revised\")"
}</pre></td>
</tr>
<tr>
<td> sample JSON input (add to specific index) </td>
<td><pre>
{
"title": "Paragraph insert revised",
{
"title": "Paragraph insert revised",
"text": "%spark\nprintln(\"Paragraph insert revised\")",
"index": 0
}
@ -636,7 +636,7 @@ limitations under the License.
<td><pre>{"status": "CREATED","message": "","body": "20151218-100330_1754029574"}</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -709,7 +709,7 @@ limitations under the License.
</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -740,7 +740,7 @@ limitations under the License.
<td><pre>{"status":"OK","message":""}</pre></td>
</tr>
</table>
<br/>
@ -772,9 +772,9 @@ limitations under the License.
<td><pre>{"status":"OK","message":""}</pre></td>
</tr>
</table>
<table class="table-configuration">
<col width="200">
<tr>
@ -826,7 +826,7 @@ limitations under the License.
}</pre></td>
</tr>
</table>
<table class="table-configuration">
<col width="200">
<tr>
@ -881,4 +881,4 @@ limitations under the License.
<td><pre>"status": "CREATED","message": "","body": "2AZPHY918"}</pre></td>
</tr>
</tr>
</table>
</table>

View file

@ -42,4 +42,4 @@ limitations under the License.
</div>
<div class="col-md-3">
</div>
</div>
</div>

View file

@ -19,10 +19,10 @@ limitations under the License.
-->
# Authentication
Authentication is company-specific.
Authentication is company-specific.
One option is to use [Basic Access Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
### HTTP Basic Authentication using NGINX
> **Quote from Wikipedia:** NGINX is a web server. It can act as a reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and an HTTP cache.
@ -33,12 +33,12 @@ Here are instructions how to accomplish the setup NGINX as a front-end authentic
This instruction based on Ubuntu 14.04 LTS but may work with other OS with few configuration changes.
1. Install NGINX server on your server instance
You can install NGINX server with same machine where zeppelin installed or separate machine where it is dedicated to serve as proxy server.
```
$ apt-get install nginx
```
```
1. Setup init script in NGINX
@ -53,11 +53,7 @@ This instruction based on Ubuntu 14.04 LTS but may work with other OS with few c
```
upstream zeppelin {
server [YOUR-ZEPPELIN-SERVER-IP]:8090;
}
upstream zeppelin-wss {
server [YOUR-ZEPPELIN-SERVER-IP]:8091;
server [YOUR-ZEPPELIN-SERVER-IP]:8080;
}
# Zeppelin Website
@ -69,32 +65,23 @@ This instruction based on Ubuntu 14.04 LTS but may work with other OS with few c
ssl_certificate [PATH-TO-YOUR-CERT-FILE]; # optional, to serve HTTPS connection
ssl_certificate_key [PATH-TO-YOUR-CERT-KEY-FILE]; # optional, to serve HTTPS connection
if ($ssl_protocol = "") {
if ($ssl_protocol = "") {
rewrite ^ https://$host$request_uri? permanent; # optional, force to use HTTPS
}
location / {
proxy_pass http://zeppelin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://zeppelin;
proxy_redirect off;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
# Zeppelin Websocket
server {
listen [YOUR-ZEPPELIN-WEBSOCKET-PORT] ssl; # add ssl is optional, to serve HTTPS connection
server_name [YOUR-ZEPPELIN-SERVER-HOST]; # for example: zeppelin.mycompany.com
ssl_certificate [PATH-TO-YOUR-CERT-FILE]; # optional, to serve HTTPS connection
ssl_certificate_key [PATH-TO-YOUR-CERT-KEY-FILE]; # optional, to serve HTTPS connection
location / {
proxy_pass http://zeppelin-wss;
location /ws {
proxy_pass http://zeppelin;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection upgrade;
@ -104,7 +91,7 @@ This instruction based on Ubuntu 14.04 LTS but may work with other OS with few c
```
Then make a symbolic link to this file from `/etc/nginx/sites-enabled/` to enable configuration above when NGINX reloads.
```
$ ln -s /etc/nginx/sites-enabled/my-basic-auth /etc/nginx/sites-available/my-basic-auth
```
@ -141,7 +128,5 @@ This instruction based on Ubuntu 14.04 LTS but may work with other OS with few c
Another option is to have an authentication server that can verify user credentials in an LDAP server.
If an incoming request to the Zeppelin server does not have a cookie with user information encrypted with the authentication server public key, the user
is redirected to the authentication server. Once the user is verified, the authentication server redirects the browser to a specific
URL in the Zeppelin server which sets the authentication cookie in the browser.
The end result is that all requests to the Zeppelin
web server have the authentication cookie which contains user and groups information.
is redirected to the authentication server. Once the user is verified, the authentication server redirects the browser to a specific URL in the Zeppelin server which sets the authentication cookie in the browser.
The end result is that all requests to the Zeppelin web server have the authentication cookie which contains user and groups information.

View file

@ -33,5 +33,5 @@ Before executing a Note operation, it checks if the user and the groups associat
operation, it checks if the user and the groups have at least one entity that belongs to the reader entities.
To initialize and modify note permissions, we provide UI like "Interpreter binding". The user inputs comma separated entities for owners, readers and writers.
We execute a rest api call with this information. In the backend we get the user information for the connection and allow the operation if the user and groups
We execute a rest api call with this information. In the backend we get the user information for the connection and allow the operation if the user and groups
associated with the current user have at least one entity that belongs to owner entities for the note.

View file

@ -5,4 +5,4 @@ title : Sitemap
{% for page in site.pages %}
{{site.production_url}}{{ page.url }}{% endfor %}
{% for post in site.posts %}
{{site.production_url}}{{ post.url }}{% endfor %}
{{site.production_url}}{{ post.url }}{% endfor %}

View file

@ -20,12 +20,12 @@ limitations under the License.
### Notebook Storage
Zeppelin has a pluggable notebook storage mechanism controlled by `zeppelin.notebook.storage` configuration option with multiple implementations.
There are few Notebook storages avaialble for a use out of the box:
There are few Notebook storages available for a use out of the box:
- (default) all notes are saved in the notebook folder in your local File System - `VFSNotebookRepo`
- there is also an option to version it using local Git repository - `GitNotebookRepo`
- another option is Amazon S3 service - `S3NotebookRepo`
Multiple storages can be used at the same time by providing a comma-separated list of the calss-names in the confiruration.
Multiple storages can be used at the same time by providing a comma-separated list of the class-names in the configuration.
By default, only first two of them will be automatically kept in sync by Zeppelin.
</br>
@ -44,7 +44,7 @@ To enable versioning for all your local notebooks though a standard Git reposito
</br>
#### Notebook Storage in S3 <a name="S3"></a>
For notebook storage in S3 you need the AWS credentials, for this there are three options, the enviroment variable ```AWS_ACCESS_KEY_ID``` and ```AWS_ACCESS_SECRET_KEY```, credentials file in the folder .aws in you home and IAM role for your instance. For complete the need steps is necessary:
For notebook storage in S3 you need the AWS credentials, for this there are three options, the environment variable ```AWS_ACCESS_KEY_ID``` and ```AWS_ACCESS_SECRET_KEY```, credentials file in the folder .aws in you home and IAM role for your instance. For complete the need steps is necessary:
</br>
you need the following folder structure on S3
@ -56,14 +56,14 @@ bucket_name/
```
set the enviroment variable in the file **zeppelin-env.sh**:
set the environment variable in the file **zeppelin-env.sh**:
```
export ZEPPELIN_NOTEBOOK_S3_BUCKET = bucket_name
export ZEPPELIN_NOTEBOOK_S3_USER = username
```
in the file **zeppelin-site.xml** uncommet and complete the next property:
in the file **zeppelin-site.xml** uncomment and complete the next property:
```
<!--If used S3 to storage, it is necessary the following folder structure bucket_name/username/notebook/-->

View file

@ -0,0 +1,139 @@
---
layout: page
title: "Zeppelin UI Layout"
description: "Description of Zeppelin UI Layout"
group: ui_layout
---
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
## Home Page
The first time you connect to Zeppelin, you'll land at the main page similar to the below screen capture
<img src="/assets/themes/zeppelin/img/ui-img/homepage.png" />
On the left of the page are listed all existing notes. Those notes are stored by default in the `$ZEPPELIN_HOME/notebook` folder.
You can filter them by name using the input text form. You can also create an new note, refresh the list of existing notes
(in case you manually copy them into the `$ZEPPELIN_HOME/notebook` folder) and import a note
<img src="/assets/themes/zeppelin/img/ui-img/notes_management.png" />
When clicking on `Import Note` link, a new dialog open. From there you can import your note from local disk or from a remote location
if you provide the URL.
<img src="/assets/themes/zeppelin/img/ui-img/note_import_dialog.png" />
By default, the name of the imported note is the same as the original note but you can override it by providing a new name
<br />
## Menus
### 1. Notebook
The `Notebook` menu proposes almost the same features as the note management section in the home page. From the drop-down menu you can:
1. Open a selected note
2. Filter node by name
3. Create a new note
<img src="/assets/themes/zeppelin/img/ui-img/notebook_menu.png" />
### 2. Interpreter
In this menu you can:
1. Configure existing **interpreter instance**
2. Add/remove **interpreter instances**
<img src="/assets/themes/zeppelin/img/ui-img/interpreter_menu.png" />
### 3. Configuration
This menu displays all the Zeppelin configuration that are set in the config file `$ZEPPELIN_HOME/conf/zeppelin-site.xml`
<img src="/assets/themes/zeppelin/img/ui-img/configuration_menu.png" />
<br />
## Note Layout
Each Zeppelin note is composed of 1 .. N paragraphs. The note can be viewed as a paragraph container.
<img src="/assets/themes/zeppelin/img/ui-img/note_paragraph_layout.png" />
### Paragraph
Each paragraph consists of 2 sections: `code section` where you put your source code and `result section` where you can see the result of the code execution.
<img src="/assets/themes/zeppelin/img/ui-img/paragraph_layout.png" />
On the top-right corner of each paragraph there are some commands to:
* execute the paragraph code
* hide/show `code section`
* hide/show `result section`
* configure the paragraph
To configure the paragraph, just click on the gear icon:
<img src="/assets/themes/zeppelin/img/ui-img/paragraph_configuration_dialog.png" />
From this dialog, you can (in descending order):
* find the **paragraph id** ( **20150924-163507_134879501** )
* control paragraph width. Since Zeppelin is using the grid system of **Twitter Bootstrap**, each paragraph width can be changed from 1 to 12
* move the paragraph 1 level up
* move the paragraph 1 level down
* create a new paragraph
* change paragraph title
* show/hide line number in the `code section`
* disable the run button for this paragraph
* export the current paragraph as an **iframe** and open the **iframe** in a new window
* clear the `result section`
* delete the current paragraph
### Note toolbar
At the top of the note, you can find a toolbar which exposes command buttons as well as configuration, security and display options
<img src="/assets/themes/zeppelin/img/ui-img/note_toolbar.png" />
On the far right is displayed the note name, just click on it to reveal the input form and update it
In the middle of the toolbar you can find the command buttons:
* execute all the paragraphs **sequentially**, in their display order
* hide/show `code section` of all paragraphs
* hide/show `result section` of all paragraphs
* clear the `result section` of all paragraphs
* clone the current note
* export the current note to a JSON file. _Please note that the `code section` and `result section` of all paragraphs will be exported. If you have heavy data in the `result section` of some paragraphs, it is recommended to clean them before exporting
* commit the current node content
* delete the note
* schedule the execution of **all paragraph** using a CRON syntax
<img src="/assets/themes/zeppelin/img/ui-img/note_commands.png" />
On the right of the note tool bar you can find configuration icons:
* display all the keyboard shorcuts
* configure the interpreters binding to the current note
* configure the note permissions
* switch the node display mode between `default`, `simple` and `report`
<img src="/assets/themes/zeppelin/img/ui-img/note_configuration.png" />

View file

@ -36,7 +36,7 @@
<properties>
<elasticsearch.version>2.1.0</elasticsearch.version>
<guava.version>18.0</guava.version>
<json-flattener.version>0.1.1</json-flattener.version>
<json-flattener.version>0.1.6</json-flattener.version>
</properties>
<dependencies>

View file

@ -17,10 +17,21 @@
package org.apache.zeppelin.elasticsearch;
import com.github.wnameless.json.flattener.JsonFlattener;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
@ -39,6 +50,7 @@ import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.InternalMultiBucketAggregation;
@ -48,9 +60,10 @@ import org.elasticsearch.search.aggregations.metrics.InternalMetricsAggregation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import com.github.wnameless.json.flattener.JsonFlattener;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
/**
@ -80,7 +93,9 @@ public class ElasticsearchInterpreter extends Interpreter {
private static final List<String> COMMANDS = Arrays.asList(
"count", "delete", "get", "help", "index", "search");
private static final Pattern FIELD_NAME_PATTERN = Pattern.compile("\\[\\\\\"(.+)\\\\\"\\](.*)");
public static final String ELASTICSEARCH_HOST = "elasticsearch.host";
public static final String ELASTICSEARCH_PORT = "elasticsearch.port";
@ -141,7 +156,7 @@ public class ElasticsearchInterpreter extends Interpreter {
@Override
public InterpreterResult interpret(String cmd, InterpreterContext interpreterContext) {
logger.info("Run Elasticsearch command '" + cmd + "'");
if (StringUtils.isEmpty(cmd) || StringUtils.isEmpty(cmd.trim())) {
return new InterpreterResult(InterpreterResult.Code.SUCCESS);
}
@ -260,15 +275,15 @@ public class ElasticsearchInterpreter extends Interpreter {
/**
* Processes a "get" request.
*
*
* @param urlItems Items of the URL
* @return Result of the get request, it contains a JSON-formatted string
*/
private InterpreterResult processGet(String[] urlItems) {
if (urlItems.length != 3
|| StringUtils.isEmpty(urlItems[0])
|| StringUtils.isEmpty(urlItems[1])
if (urlItems.length != 3
|| StringUtils.isEmpty(urlItems[0])
|| StringUtils.isEmpty(urlItems[1])
|| StringUtils.isEmpty(urlItems[2])) {
return new InterpreterResult(InterpreterResult.Code.ERROR,
"Bad URL (it should be /index/type/id)");
@ -285,13 +300,13 @@ public class ElasticsearchInterpreter extends Interpreter {
InterpreterResult.Type.TEXT,
json);
}
return new InterpreterResult(InterpreterResult.Code.ERROR, "Document not found");
}
/**
* Processes a "count" request.
*
*
* @param urlItems Items of the URL
* @param data May contains the JSON of the request
* @return Result of the count request, it contains the total hits
@ -313,7 +328,7 @@ public class ElasticsearchInterpreter extends Interpreter {
/**
* Processes a "search" request.
*
*
* @param urlItems Items of the URL
* @param data May contains the JSON of the request
* @param size Limit of result set
@ -325,7 +340,7 @@ public class ElasticsearchInterpreter extends Interpreter {
return new InterpreterResult(InterpreterResult.Code.ERROR,
"Bad URL (it should be /index1,index2,.../type1,type2,...)");
}
final SearchResponse response = searchData(urlItems, data, size);
return buildResponseMessage(response);
@ -333,18 +348,18 @@ public class ElasticsearchInterpreter extends Interpreter {
/**
* Processes a "index" request.
*
*
* @param urlItems Items of the URL
* @param data JSON to be indexed
* @return Result of the index request, it contains the id of the document
*/
private InterpreterResult processIndex(String[] urlItems, String data) {
if (urlItems.length < 2 || urlItems.length > 3) {
return new InterpreterResult(InterpreterResult.Code.ERROR,
"Bad URL (it should be /index/type or /index/type/id)");
}
final IndexResponse response = client
.prepareIndex(urlItems[0], urlItems[1], urlItems.length == 2 ? null : urlItems[2])
.setSource(data)
@ -358,15 +373,15 @@ public class ElasticsearchInterpreter extends Interpreter {
/**
* Processes a "delete" request.
*
*
* @param urlItems Items of the URL
* @return Result of the delete request, it contains the id of the deleted document
*/
private InterpreterResult processDelete(String[] urlItems) {
if (urlItems.length != 3
|| StringUtils.isEmpty(urlItems[0])
|| StringUtils.isEmpty(urlItems[1])
if (urlItems.length != 3
|| StringUtils.isEmpty(urlItems[0])
|| StringUtils.isEmpty(urlItems[1])
|| StringUtils.isEmpty(urlItems[2])) {
return new InterpreterResult(InterpreterResult.Code.ERROR,
"Bad URL (it should be /index/type/id)");
@ -375,23 +390,23 @@ public class ElasticsearchInterpreter extends Interpreter {
final DeleteResponse response = client
.prepareDelete(urlItems[0], urlItems[1], urlItems[2])
.get();
if (response.isFound()) {
return new InterpreterResult(
InterpreterResult.Code.SUCCESS,
InterpreterResult.Type.TEXT,
response.getId());
}
return new InterpreterResult(InterpreterResult.Code.ERROR, "Document not found");
}
private SearchResponse searchData(String[] urlItems, String query, int size) {
final SearchRequestBuilder reqBuilder = new SearchRequestBuilder(
client, SearchAction.INSTANCE);
reqBuilder.setIndices();
if (urlItems.length >= 1) {
reqBuilder.setIndices(StringUtils.split(urlItems[0], ","));
}
@ -452,18 +467,42 @@ public class ElasticsearchInterpreter extends Interpreter {
}
private String buildSearchHitsResponseMessage(SearchHit[] hits) {
if (hits == null || hits.length == 0) {
return "";
}
//First : get all the keys in order to build an ordered list of the values for each hit
//
final Map<String, Object> hitFields = new HashMap<>();
final List<Map<String, Object>> flattenHits = new LinkedList<>();
final Set<String> keys = new TreeSet<>();
for (SearchHit hit : hits) {
final String json = hit.getSourceAsString();
final Map<String, Object> flattenMap = JsonFlattener.flattenAsMap(json);
// Fields can be found either in _source, or in fields (it depends on the query)
//
String json = hit.getSourceAsString();
if (json == null) {
hitFields.clear();
for (SearchHitField hitField : hit.getFields().values()) {
hitFields.put(hitField.getName(), hitField.getValues());
}
json = gson.toJson(hitFields);
}
final Map<String, Object> flattenJsonMap = JsonFlattener.flattenAsMap(json);
final Map<String, Object> flattenMap = new HashMap<>();
for (Iterator<String> iter = flattenJsonMap.keySet().iterator(); iter.hasNext(); ) {
// Replace keys that match a format like that : [\"keyname\"][0]
final String fieldName = iter.next();
final Matcher fieldNameMatcher = FIELD_NAME_PATTERN.matcher(fieldName);
if (fieldNameMatcher.matches()) {
flattenMap.put(fieldNameMatcher.group(1) + fieldNameMatcher.group(2),
flattenJsonMap.get(fieldName));
}
else {
flattenMap.put(fieldName, flattenJsonMap.get(fieldName));
}
}
flattenHits.add(flattenMap);
for (String key : flattenMap.keySet()) {

View file

@ -17,6 +17,15 @@
package org.apache.zeppelin.elasticsearch;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import java.util.UUID;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
@ -29,21 +38,12 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import java.util.UUID;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.junit.Assert.assertEquals;
public class ElasticsearchInterpreterTest {
private static Client elsClient;
private static Node elsNode;
private static ElasticsearchInterpreter interpreter;
private static final String[] METHODS = { "GET", "PUT", "DELETE", "POST" };
private static final int[] STATUS = { 200, 404, 500, 403 };
@ -75,7 +75,7 @@ public class ElasticsearchInterpreterTest {
.field("type", "integer")
.endObject()
.endObject().endObject().endObject()).get();
for (int i = 0; i < 50; i++) {
elsClient.prepareIndex("logs", "http", "" + i)
.setRefresh(true)
@ -100,7 +100,7 @@ public class ElasticsearchInterpreterTest {
interpreter = new ElasticsearchInterpreter(props);
interpreter.open();
}
@AfterClass
public static void clean() {
if (interpreter != null) {
@ -116,41 +116,44 @@ public class ElasticsearchInterpreterTest {
elsNode.close();
}
}
@Test
public void testCount() {
InterpreterResult res = interpreter.interpret("count /unknown", null);
assertEquals(Code.ERROR, res.code());
res = interpreter.interpret("count /logs", null);
assertEquals("50", res.message());
}
@Test
public void testGet() {
InterpreterResult res = interpreter.interpret("get /logs/http/unknown", null);
assertEquals(Code.ERROR, res.code());
res = interpreter.interpret("get /logs/http/10", null);
assertEquals(Code.SUCCESS, res.code());
}
@Test
public void testSearch() {
InterpreterResult res = interpreter.interpret("size 10\nsearch /logs *", null);
assertEquals(Code.SUCCESS, res.code());
res = interpreter.interpret("search /logs {{{hello}}}", null);
assertEquals(Code.ERROR, res.code());
res = interpreter.interpret("search /logs { \"query\": { \"match\": { \"status\": 500 } } }", null);
assertEquals(Code.SUCCESS, res.code());
res = interpreter.interpret("search /logs status:404", null);
assertEquals(Code.SUCCESS, res.code());
assertEquals(Code.SUCCESS, res.code());
res = interpreter.interpret("search /logs { \"fields\": [ \"date\", \"request.headers\" ], \"query\": { \"match\": { \"status\": 500 } } }", null);
assertEquals(Code.SUCCESS, res.code());
}
@Test
@ -177,23 +180,23 @@ public class ElasticsearchInterpreterTest {
" { \"terms\" : { \"field\" : \"status\" } } } }", null);
assertEquals(Code.SUCCESS, res.code());
}
@Test
public void testIndex() {
InterpreterResult res = interpreter.interpret("index /logs { \"date\": \"" + new Date() + "\", \"method\": \"PUT\", \"status\": \"500\" }", null);
assertEquals(Code.ERROR, res.code());
res = interpreter.interpret("index /logs/http { \"date\": \"2015-12-06T14:54:23.368Z\", \"method\": \"PUT\", \"status\": \"500\" }", null);
assertEquals(Code.SUCCESS, res.code());
}
@Test
public void testDelete() {
InterpreterResult res = interpreter.interpret("delete /logs/http/unknown", null);
assertEquals(Code.ERROR, res.code());
res = interpreter.interpret("delete /logs/http/11", null);
assertEquals("11", res.message());
}

View file

@ -262,7 +262,7 @@ public class DepInterpreter extends Interpreter {
public List<String> completion(String buf, int cursor) {
ScalaCompleter c = completor.completer();
Candidates ret = c.complete(buf, cursor);
return scala.collection.JavaConversions.asJavaList(ret.candidates());
return scala.collection.JavaConversions.seqAsJavaList(ret.candidates());
}
private List<File> currentClassPath() {

View file

@ -672,7 +672,7 @@ public class SparkInterpreter extends Interpreter {
}
ScalaCompleter c = completor.completer();
Candidates ret = c.complete(completionText, cursor);
return scala.collection.JavaConversions.asJavaList(ret.candidates());
return scala.collection.JavaConversions.seqAsJavaList(ret.candidates());
}
private String getCompletionTargetString(String text, int cursor) {
@ -901,14 +901,14 @@ public class SparkInterpreter extends Interpreter {
Object completedTaskInfo = null;
completedTaskInfo = JavaConversions.asJavaMap(
completedTaskInfo = JavaConversions.mapAsJavaMap(
(HashMap<Object, Object>) sparkListener.getClass()
.getMethod("stageIdToTasksComplete").invoke(sparkListener)).get(id);
if (completedTaskInfo != null) {
completedTasks += (int) completedTaskInfo;
}
List<Object> parents = JavaConversions.asJavaList((Seq<Object>) stage.getClass()
List<Object> parents = JavaConversions.seqAsJavaList((Seq<Object>) stage.getClass()
.getMethod("parents").invoke(stage));
if (parents != null) {
for (Object s : parents) {
@ -937,7 +937,7 @@ public class SparkInterpreter extends Interpreter {
Method numCompletedTasks = stageUIDataClass.getMethod("numCompleteTasks");
Set<Tuple2<Object, Object>> keys =
JavaConverters.asJavaSetConverter(stageIdData.keySet()).asJava();
JavaConverters.setAsJavaSetConverter(stageIdData.keySet()).asJava();
for (Tuple2<Object, Object> k : keys) {
if (id == (int) k._1()) {
Object uiData = stageIdData.get(k).get();
@ -948,7 +948,7 @@ public class SparkInterpreter extends Interpreter {
logger.error("Error on getting progress information", e);
}
List<Object> parents = JavaConversions.asJavaList((Seq<Object>) stage.getClass()
List<Object> parents = JavaConversions.seqAsJavaList((Seq<Object>) stage.getClass()
.getMethod("parents").invoke(stage));
if (parents != null) {
for (Object s : parents) {

View file

@ -19,7 +19,7 @@ package org.apache.zeppelin.spark;
import static scala.collection.JavaConversions.asJavaCollection;
import static scala.collection.JavaConversions.asJavaIterable;
import static scala.collection.JavaConversions.asScalaIterable;
import static scala.collection.JavaConversions.collectionAsScalaIterable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@ -95,13 +95,13 @@ public class ZeppelinContext {
for (Tuple2<Object, String> option : asJavaIterable(options)) {
allChecked.add(option._1());
}
return checkbox(name, asScalaIterable(allChecked), options);
return checkbox(name, collectionAsScalaIterable(allChecked), options);
}
public scala.collection.Iterable<Object> checkbox(String name,
scala.collection.Iterable<Object> defaultChecked,
scala.collection.Iterable<Tuple2<Object, String>> options) {
return asScalaIterable(gui.checkbox(name, asJavaCollection(defaultChecked),
return collectionAsScalaIterable(gui.checkbox(name, asJavaCollection(defaultChecked),
tuplesToParamOptions(options)));
}

View file

@ -83,14 +83,14 @@ class PyZeppelinContext(dict):
def select(self, name, options, defaultValue = ""):
# auto_convert to ArrayList doesn't match the method signature on JVM side
tuples = map(lambda items: self.__tupleToScalaTuple2(items), options)
tuples = list(map(lambda items: self.__tupleToScalaTuple2(items), options))
iterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(tuples)
return self.z.select(name, defaultValue, iterables)
def checkbox(self, name, options, defaultChecked = None):
if defaultChecked is None:
defaultChecked = map(lambda items: items[0], options)
optionTuples = map(lambda items: self.__tupleToScalaTuple2(items), options)
defaultChecked = list(map(lambda items: items[0], options))
optionTuples = list(map(lambda items: self.__tupleToScalaTuple2(items), options))
optionIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(optionTuples)
defaultCheckedIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(defaultChecked)

View file

@ -73,7 +73,7 @@ The following components are provided under Apache License.
(Apache 2.0) Jackson-dataformat-CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.6.2 - http://wiki.fasterxml.com/JacksonForCbor)
(Apache 2.0) Jackson-dataformat-Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.6.2 - http://wiki.fasterxml.com/JacksonForSmile)
(Apache 2.0) Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.6.2 - https://github.com/FasterXML/jackson)
(Apache 2.0) json-flattener (com.github.wnameless:json-flattener:0.1.1 - https://github.com/wnameless/json-flattener)
(Apache 2.0) json-flattener (com.github.wnameless:json-flattener:0.1.6 - https://github.com/wnameless/json-flattener)
(Apache 2.0) Spatial4J (com.spatial4j:spatial4j:0.4.1 - https://github.com/spatial4j/spatial4j)
(Apache 2.0) T-Digest (com.tdunning:t-digest:3.0 - https://github.com/tdunning/t-digest)
(Apache 2.0) Netty (io.netty:netty:3.10.5.Final - http://netty.io/)

View file

@ -35,7 +35,7 @@
<properties>
<cxf.version>2.7.7</cxf.version>
<jetty.version>8.1.14.v20131031</jetty.version>
<jetty.version>9.2.15.v20160210</jetty.version>
<commons.httpclient.version>4.3.6</commons.httpclient.version>
</properties>
@ -129,11 +129,16 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.aggregate</groupId>
<artifactId>jetty-all-server</artifactId>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<!--
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
@ -165,7 +170,7 @@
</exclusion>
</exclusions>
</dependency>
-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@ -237,6 +242,10 @@
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View file

@ -94,14 +94,13 @@ public class InterpreterRestApi {
NewInterpreterSettingRequest.class);
Properties p = new Properties();
p.putAll(request.getProperties());
InterpreterGroup interpreterGroup = interpreterFactory.add(request.getName(),
InterpreterSetting interpreterSetting = interpreterFactory.add(request.getName(),
request.getGroup(),
request.getDependencies(),
request.getOption(),
p);
InterpreterSetting setting = interpreterFactory.get(interpreterGroup.getId());
logger.info("new setting created with {}", setting.id());
return new JsonResponse(Status.CREATED, "", setting).build();
logger.info("new setting created with {}", interpreterSetting.id());
return new JsonResponse(Status.CREATED, "", interpreterSetting).build();
} catch (InterpreterException e) {
logger.error("Exception in InterpreterRestApi while creating ", e);
return new JsonResponse(

View file

@ -50,6 +50,7 @@ import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.GsonBuilder;
@ -127,9 +128,29 @@ public class NotebookRestApi {
return new JsonResponse<>(Status.FORBIDDEN, ownerPermissionError(userAndRoles,
notebookAuthorization.getOwners(noteId))).build();
}
notebookAuthorization.setOwners(noteId, permMap.get("owners"));
notebookAuthorization.setReaders(noteId, permMap.get("readers"));
notebookAuthorization.setWriters(noteId, permMap.get("writers"));
HashSet readers = permMap.get("readers");
HashSet owners = permMap.get("owners");
HashSet writers = permMap.get("writers");
// Set readers, if writers and owners is empty -> set to user requesting the change
if (readers != null && !readers.isEmpty()) {
if (writers.isEmpty()) {
writers = Sets.newHashSet(SecurityUtils.getPrincipal());
}
if (owners.isEmpty()) {
owners = Sets.newHashSet(SecurityUtils.getPrincipal());
}
}
// Set writers, if owners is empty -> set to user requesting the change
if ( writers != null && !writers.isEmpty()) {
if (owners.isEmpty()) {
owners = Sets.newHashSet(SecurityUtils.getPrincipal());
}
}
notebookAuthorization.setReaders(noteId, readers);
notebookAuthorization.setWriters(noteId, writers);
notebookAuthorization.setOwners(noteId, owners);
LOG.debug("After set permissions {} {} {}",
notebookAuthorization.getOwners(noteId),
notebookAuthorization.getReaders(noteId),
@ -644,14 +665,29 @@ public class NotebookRestApi {
}
/**
* Search for a Notes
* Search for a Notes with permissions
*/
@GET
@Path("search")
public Response search(@QueryParam("q") String queryTerm) {
LOG.info("Searching notebooks for: {}", queryTerm);
String principal = SecurityUtils.getPrincipal();
HashSet<String> roles = SecurityUtils.getRoles();
HashSet<String> userAndRoles = new HashSet<String>();
userAndRoles.add(principal);
userAndRoles.addAll(roles);
List<Map<String, String>> notebooksFound = notebookIndex.query(queryTerm);
LOG.info("{} notbooks found", notebooksFound.size());
for (int i = 0; i < notebooksFound.size(); i++) {
String[] Id = notebooksFound.get(i).get("id").split("/", 2);
String noteId = Id[0];
if (!notebookAuthorization.isOwner(noteId, userAndRoles) &&
!notebookAuthorization.isReader(noteId, userAndRoles) &&
!notebookAuthorization.isWriter(noteId, userAndRoles)) {
notebooksFound.remove(i);
i--;
}
}
LOG.info("{} notebooks found", notebooksFound.size());
return new JsonResponse<>(Status.OK, notebooksFound).build();
}

View file

@ -31,13 +31,10 @@ import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.apache.zeppelin.search.LuceneSearch;
import org.apache.zeppelin.search.SearchService;
import org.apache.zeppelin.socket.NotebookServer;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
@ -47,7 +44,6 @@ import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.servlet.DispatcherType;
import javax.ws.rs.core.Application;
import java.io.File;
@ -58,7 +54,6 @@ import java.util.Set;
/**
* Main class of Zeppelin.
*
*/
public class ZeppelinServer extends Application {
private static final Logger LOG = LoggerFactory.getLogger(ZeppelinServer.class);
@ -91,24 +86,26 @@ public class ZeppelinServer extends Application {
}
public static void main(String[] args) throws InterruptedException {
ZeppelinConfiguration conf = ZeppelinConfiguration.create();
conf.setProperty("args", args);
// REST api
final ServletContextHandler restApiContext = setupRestApiContextHandler(conf);
jettyWebServer = setupJettyServer(conf);
// Notebook server
final ServletContextHandler notebookContext = setupNotebookServer(conf);
ContextHandlerCollection contexts = new ContextHandlerCollection();
jettyWebServer.setHandler(contexts);
// Web UI
final WebAppContext webApp = setupWebAppContext(conf);
final WebAppContext webApp = setupWebAppContext(contexts, conf);
// add all handlers
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(new Handler[]{restApiContext, notebookContext, webApp});
// REST api
setupRestApiContextHandler(webApp, conf);
jettyWebServer = setupJettyServer(conf);
jettyWebServer.setHandler(contexts);
// Notebook server
setupNotebookServer(webApp, conf);
//Below is commented since zeppelin-docs module is removed.
//final WebAppContext webAppSwagg = setupWebAppSwagger(conf);
LOG.info("Starting zeppelin server");
try {
@ -150,27 +147,50 @@ public class ZeppelinServer extends Application {
}
private static Server setupJettyServer(ZeppelinConfiguration conf) {
AbstractConnector connector;
final Server server = new Server();
ServerConnector connector;
if (conf.useSsl()) {
connector = new SslSelectChannelConnector(getSslContextFactory(conf));
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(conf.getServerPort());
httpConfig.setOutputBufferSize(32768);
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
SecureRequestCustomizer src = new SecureRequestCustomizer();
// Only with Jetty 9.3.x
// src.setStsMaxAge(2000);
// src.setStsIncludeSubDomains(true);
httpsConfig.addCustomizer(src);
connector = new ServerConnector(
server,
new SslConnectionFactory(getSslContextFactory(conf), HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig));
} else {
connector = new SelectChannelConnector();
connector = new ServerConnector(server);
}
// Set some timeout options to make debugging easier.
int timeout = 1000 * 30;
connector.setMaxIdleTime(timeout);
connector.setIdleTimeout(timeout);
connector.setSoLingerTime(-1);
connector.setHost(conf.getServerAddress());
connector.setPort(conf.getServerPort());
final Server server = new Server();
server.addConnector(connector);
return server;
}
private static ServletContextHandler setupNotebookServer(ZeppelinConfiguration conf) {
private static void setupNotebookServer(WebAppContext webapp,
ZeppelinConfiguration conf) {
notebookWsServer = new NotebookServer();
String maxTextMessageSize = conf.getWebsocketMaxTextMessageSize();
final ServletHolder servletHolder = new ServletHolder(notebookWsServer);
@ -179,28 +199,20 @@ public class ZeppelinServer extends Application {
final ServletContextHandler cxfContext = new ServletContextHandler(
ServletContextHandler.SESSIONS);
cxfContext.setSessionHandler(new SessionHandler());
cxfContext.setContextPath(conf.getServerContextPath());
cxfContext.addServlet(servletHolder, "/ws/*");
cxfContext.addFilter(new FilterHolder(CorsFilter.class), "/*",
EnumSet.allOf(DispatcherType.class));
return cxfContext;
webapp.addServlet(servletHolder, "/ws/*");
}
@SuppressWarnings("deprecation")
private static SslContextFactory getSslContextFactory(ZeppelinConfiguration conf) {
// Note that the API for the SslContextFactory is different for
// Jetty version 9
SslContextFactory sslContextFactory = new SslContextFactory();
// Set keystore
sslContextFactory.setKeyStore(conf.getKeyStorePath());
sslContextFactory.setKeyStorePath(conf.getKeyStorePath());
sslContextFactory.setKeyStoreType(conf.getKeyStoreType());
sslContextFactory.setKeyStorePassword(conf.getKeyStorePassword());
sslContextFactory.setKeyManagerPassword(conf.getKeyManagerPassword());
// Set truststore
sslContextFactory.setTrustStore(conf.getTrustStorePath());
sslContextFactory.setTrustStorePath(conf.getTrustStorePath());
sslContextFactory.setTrustStoreType(conf.getTrustStoreType());
sslContextFactory.setTrustStorePassword(conf.getTrustStorePassword());
@ -209,44 +221,32 @@ public class ZeppelinServer extends Application {
return sslContextFactory;
}
@SuppressWarnings("unused") //TODO(bzz) why unused?
private static SSLContext getSslContext(ZeppelinConfiguration conf)
throws Exception {
private static void setupRestApiContextHandler(WebAppContext webapp,
ZeppelinConfiguration conf) {
SslContextFactory scf = getSslContextFactory(conf);
if (!scf.isStarted()) {
scf.start();
}
return scf.getSslContext();
}
private static ServletContextHandler setupRestApiContextHandler(ZeppelinConfiguration conf) {
final ServletHolder cxfServletHolder = new ServletHolder(new CXFNonSpringJaxrsServlet());
cxfServletHolder.setInitParameter("javax.ws.rs.Application", ZeppelinServer.class.getName());
cxfServletHolder.setName("rest");
cxfServletHolder.setForcedPath("rest");
final ServletContextHandler cxfContext = new ServletContextHandler();
cxfContext.setSessionHandler(new SessionHandler());
cxfContext.setContextPath(conf.getServerContextPath());
cxfContext.addServlet(cxfServletHolder, "/api/*");
webapp.setSessionHandler(new SessionHandler());
webapp.addServlet(cxfServletHolder, "/api/*");
cxfContext.addFilter(new FilterHolder(CorsFilter.class), "/*",
webapp.addFilter(new FilterHolder(CorsFilter.class), "/*",
EnumSet.allOf(DispatcherType.class));
cxfContext.setInitParameter("shiroConfigLocations",
webapp.setInitParameter("shiroConfigLocations",
new File(conf.getShiroPath()).toURI().toString());
cxfContext.addFilter(org.apache.shiro.web.servlet.ShiroFilter.class, "/*",
webapp.addFilter(org.apache.shiro.web.servlet.ShiroFilter.class, "/*",
EnumSet.allOf(DispatcherType.class));
cxfContext.addEventListener(new org.apache.shiro.web.env.EnvironmentLoaderListener());
webapp.addEventListener(new org.apache.shiro.web.env.EnvironmentLoaderListener());
return cxfContext;
}
private static WebAppContext setupWebAppContext(
ZeppelinConfiguration conf) {
private static WebAppContext setupWebAppContext(ContextHandlerCollection contexts,
ZeppelinConfiguration conf) {
WebAppContext webApp = new WebAppContext();
webApp.setContextPath(conf.getServerContextPath());
@ -266,7 +266,10 @@ public class ZeppelinServer extends Application {
}
// Explicit bind to root
webApp.addServlet(new ServletHolder(new DefaultServlet()), "/*");
contexts.addHandler(webApp);
return webApp;
}
@Override

View file

@ -16,7 +16,6 @@
*/
package org.apache.zeppelin.socket;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
@ -48,15 +47,14 @@ import org.apache.zeppelin.server.ZeppelinServer;
import org.apache.zeppelin.socket.Message.OP;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.utils.SecurityUtils;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Zeppelin websocket service.
*
*/
public class NotebookServer extends WebSocketServlet implements
NotebookSocketListener, JobListenerFactory, AngularObjectRegistryListener,
@ -71,6 +69,10 @@ public class NotebookServer extends WebSocketServlet implements
}
@Override
public void configure(WebSocketServletFactory factory) {
factory.setCreator(new NotebookWebSocketCreator(this));
}
public boolean checkOrigin(HttpServletRequest request, String origin) {
try {
return SecurityUtils.isValidOrigin(origin, ZeppelinConfiguration.create());
@ -82,8 +84,7 @@ public class NotebookServer extends WebSocketServlet implements
return false;
}
@Override
public WebSocket doWebSocketConnect(HttpServletRequest req, String protocol) {
public NotebookSocket doWebSocketConnect(HttpServletRequest req, String protocol) {
return new NotebookSocket(req, protocol, this);
}
@ -132,7 +133,7 @@ public class NotebookServer extends WebSocketServlet implements
/** Lets be elegant here */
switch (messagereceived.op) {
case LIST_NOTES:
broadcastNoteList();
unicastNoteList(conn);
break;
case RELOAD_NOTES_FROM_REPO:
broadcastReloadedNoteList();
@ -200,7 +201,6 @@ public class NotebookServer extends WebSocketServlet implements
checkpointNotebook(conn, notebook, messagereceived);
break;
default:
broadcastNoteList();
break;
}
} catch (Exception e) {
@ -339,6 +339,14 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void unicast(Message m, NotebookSocket conn) {
try {
conn.send(serializeMessage(m));
} catch (IOException e) {
LOG.error("socket error", e);
}
}
public List<Map<String, String>> generateNotebooksInfo(boolean needsReload) {
Notebook notebook = notebook();
@ -381,6 +389,11 @@ public class NotebookServer extends WebSocketServlet implements
broadcastAll(new Message(OP.NOTES_INFO).put("notes", notesInfo));
}
public void unicastNoteList(NotebookSocket conn) {
List<Map<String, String>> notesInfo = generateNotebooksInfo(false);
unicast(new Message(OP.NOTES_INFO).put("notes", notesInfo), conn);
}
public void broadcastReloadedNoteList() {
List<Map<String, String>> notesInfo = generateNotebooksInfo(true);
broadcastAll(new Message(OP.NOTES_INFO).put("notes", notesInfo));
@ -414,7 +427,6 @@ public class NotebookServer extends WebSocketServlet implements
if (note != null) {
if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
permissionError(conn, "read", userAndRoles, notebookAuthorization.getReaders(noteId));
broadcastNoteList();
return;
}
addConnectionToNote(note.id(), conn);
@ -436,7 +448,6 @@ public class NotebookServer extends WebSocketServlet implements
NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
permissionError(conn, "read", userAndRoles, notebookAuthorization.getReaders(noteId));
broadcastNoteList();
return;
}
addConnectionToNote(note.id(), conn);
@ -448,7 +459,7 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void updateNote(WebSocket conn, HashSet<String> userAndRoles,
private void updateNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
String noteId = (String) fromMessage.get("id");
@ -462,6 +473,12 @@ public class NotebookServer extends WebSocketServlet implements
return;
}
NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
permissionError(conn, "update", userAndRoles, notebookAuthorization.getWriters(noteId));
return;
}
Note note = notebook.getNote(noteId);
if (note != null) {
boolean cronUpdated = isCronUpdated(config, note.getConfig());
@ -663,12 +680,12 @@ public class NotebookServer extends WebSocketServlet implements
List<InterpreterSetting> settings = note.getNoteReplLoader()
.getInterpreterSettings();
for (InterpreterSetting setting : settings) {
if (setting.getInterpreterGroup() == null) {
if (setting.getInterpreterGroup(note.id()) == null) {
continue;
}
if (interpreterGroupId.equals(setting.getInterpreterGroup().getId())) {
if (interpreterGroupId.equals(setting.getInterpreterGroup(note.id()).getId())) {
AngularObjectRegistry angularObjectRegistry = setting
.getInterpreterGroup().getAngularObjectRegistry();
.getInterpreterGroup(note.id()).getAngularObjectRegistry();
// first trying to get local registry
ao = angularObjectRegistry.get(varName, noteId, paragraphId);
if (ao == null) {
@ -704,12 +721,12 @@ public class NotebookServer extends WebSocketServlet implements
List<InterpreterSetting> settings = note.getNoteReplLoader()
.getInterpreterSettings();
for (InterpreterSetting setting : settings) {
if (setting.getInterpreterGroup() == null) {
if (setting.getInterpreterGroup(n.id()) == null) {
continue;
}
if (interpreterGroupId.equals(setting.getInterpreterGroup().getId())) {
if (interpreterGroupId.equals(setting.getInterpreterGroup(n.id()).getId())) {
AngularObjectRegistry angularObjectRegistry = setting
.getInterpreterGroup().getAngularObjectRegistry();
.getInterpreterGroup(n.id()).getAngularObjectRegistry();
this.broadcastExcept(
n.id(),
new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao)
@ -1145,14 +1162,14 @@ public class NotebookServer extends WebSocketServlet implements
}
for (InterpreterSetting intpSetting : settings) {
AngularObjectRegistry registry = intpSetting.getInterpreterGroup()
AngularObjectRegistry registry = intpSetting.getInterpreterGroup(note.id())
.getAngularObjectRegistry();
List<AngularObject> objects = registry.getAllWithGlobal(note.id());
for (AngularObject object : objects) {
conn.send(serializeMessage(new Message(OP.ANGULAR_OBJECT_UPDATE)
.put("angularObject", object)
.put("interpreterGroupId",
intpSetting.getInterpreterGroup().getId())
intpSetting.getInterpreterGroup(note.id()).getId())
.put("noteId", note.id())
.put("paragraphId", object.getParagraphId())
));
@ -1183,7 +1200,7 @@ public class NotebookServer extends WebSocketServlet implements
if (intpSettings.isEmpty())
continue;
for (InterpreterSetting setting : intpSettings) {
if (setting.getInterpreterGroup().getId().equals(interpreterGroupId)) {
if (setting.getInterpreterGroup(note.id()).getId().equals(interpreterGroupId)) {
broadcast(
note.id(),
new Message(OP.ANGULAR_OBJECT_UPDATE)

View file

@ -20,19 +20,19 @@ import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/**
* Notebook websocket
*/
public class NotebookSocket implements WebSocket.OnTextMessage{
public class NotebookSocket extends WebSocketAdapter {
private Connection connection;
private Session connection;
private NotebookSocketListener listener;
private HttpServletRequest request;
private String protocol;
public NotebookSocket(HttpServletRequest req, String protocol,
NotebookSocketListener listener) {
this.listener = listener;
@ -41,22 +41,22 @@ public class NotebookSocket implements WebSocket.OnTextMessage{
}
@Override
public void onClose(int closeCode, String message) {
public void onWebSocketClose(int closeCode, String message) {
listener.onClose(this, closeCode, message);
}
@Override
public void onOpen(Connection connection) {
public void onWebSocketConnect(Session connection) {
this.connection = connection;
listener.onOpen(this);
}
@Override
public void onMessage(String message) {
public void onWebSocketText(String message) {
listener.onMessage(this, message);
}
public HttpServletRequest getRequest() {
return request;
}
@ -66,8 +66,7 @@ public class NotebookSocket implements WebSocket.OnTextMessage{
}
public void send(String serializeMessage) throws IOException {
connection.sendMessage(serializeMessage);
connection.getRemote().sendString(serializeMessage);
}
}

View file

@ -20,7 +20,7 @@ package org.apache.zeppelin.socket;
* NoteboookSocket listener
*/
public interface NotebookSocketListener {
public void onClose(NotebookSocket socket, int code, String message);
public void onOpen(NotebookSocket socket);
public void onMessage(NotebookSocket socket, String message);
void onClose(NotebookSocket socket, int code, String message);
void onOpen(NotebookSocket socket);
void onMessage(NotebookSocket socket, String message);
}

View file

@ -0,0 +1,36 @@
/*
* 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.socket;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
/**
* Responsible to create the WebSockets for the NotebookServer.
*/
public class NotebookWebSocketCreator implements WebSocketCreator {
private NotebookServer notebookServer;
public NotebookWebSocketCreator(NotebookServer notebookServer) {
this.notebookServer = notebookServer;
}
public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response) {
return new NotebookSocket(request.getHttpServletRequest(), "", notebookServer);
}
}

View file

@ -205,7 +205,8 @@ public class ZeppelinIT extends AbstractZeppelinIT {
String artifact = "org.apache.commons:commons-csv:1.1";
depArtifact.sendKeys(artifact);
driver.findElement(By.xpath("//button[contains(.,'Save')]")).submit();
driver.switchTo().alert().accept();
driver.findElement(By.xpath("//div[@class='modal-dialog'][contains(.,'Do you want to update this interpreter and restart with new settings?')]" +
"//div[@class='modal-footer']//button[contains(.,'OK')]")).click();
driver.navigate().back();
createNewNote();
@ -241,7 +242,8 @@ public class ZeppelinIT extends AbstractZeppelinIT {
sleep(5000, true);
testDepRemoveBtn.click();
driver.findElement(By.xpath("//button[contains(.,'Save')]")).submit();
driver.switchTo().alert().accept();
driver.findElement(By.xpath("//div[@class='modal-dialog'][contains(.,'Do you want to update this interpreter and restart with new settings?')]" +
"//div[@class='modal-footer']//button[contains(.,'OK')]")).click();
} catch (Exception e) {
handleException("Exception in ZeppelinIT while testSparkInterpreterDependencyLoading ", e);
}

View file

@ -374,13 +374,13 @@ public abstract class AbstractTestRestApi {
//Create new Setting and return Setting ID
protected String createTempSetting(String tempName)
throws IOException, RepositoryException {
InterpreterGroup interpreterGroup = ZeppelinServer.notebook.getInterpreterFactory()
InterpreterSetting setting = ZeppelinServer.notebook.getInterpreterFactory()
.add(tempName,
"newGroup",
new LinkedList<Dependency>(),
new InterpreterOption(false),
new Properties());
return interpreterGroup.getId();
return setting.id();
}
protected TypeSafeMatcher<? super JsonElement> hasRootElementNamed(final String memberName) {

View file

@ -0,0 +1,127 @@
/*
* 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.rest;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.NotebookAuthorizationInfoSaving;
import org.apache.zeppelin.server.ZeppelinServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Zeppelin notebook rest api tests
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class NotebookRestApiTest extends AbstractTestRestApi {
Gson gson = new Gson();
@BeforeClass
public static void init() throws Exception {
AbstractTestRestApi.startUp();
}
@AfterClass
public static void destroy() throws Exception {
AbstractTestRestApi.shutDown();
}
@Test
public void testPermissions() throws IOException {
Note note1 = ZeppelinServer.notebook.createNote();
// Set only readers
String jsonRequest = "{\"readers\":[\"admin-team\"],\"owners\":[]," +
"\"writers\":[]}";
PutMethod put = httpPut("/notebook/" + note1.getId() + "/permissions/", jsonRequest);
LOG.info("testPermissions response\n" + put.getResponseBodyAsString());
assertThat("test update method:", put, isAllowed());
put.releaseConnection();
GetMethod get = httpGet("/notebook/" + note1.getId() + "/permissions/");
assertThat(get, isAllowed());
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, Set<String>> authInfo = (Map<String, Set<String>>) resp.get("body");
// Check that both owners and writers is set to the princpal if empty
assertEquals(authInfo.get("readers"), Lists.newArrayList("admin-team"));
assertEquals(authInfo.get("owners"), Lists.newArrayList("anonymous"));
assertEquals(authInfo.get("writers"), Lists.newArrayList("anonymous"));
get.releaseConnection();
Note note2 = ZeppelinServer.notebook.createNote();
// Set only writers
jsonRequest = "{\"readers\":[],\"owners\":[]," +
"\"writers\":[\"admin-team\"]}";
put = httpPut("/notebook/" + note2.getId() + "/permissions/", jsonRequest);
assertThat("test update method:", put, isAllowed());
put.releaseConnection();
get = httpGet("/notebook/" + note2.getId() + "/permissions/");
assertThat(get, isAllowed());
resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
authInfo = (Map<String, Set<String>>) resp.get("body");
// Check that owners is set to the princpal if empty
assertEquals(authInfo.get("owners"), Lists.newArrayList("anonymous"));
assertEquals(authInfo.get("writers"), Lists.newArrayList("admin-team"));
get.releaseConnection();
// Test clear permissions
jsonRequest = "{\"readers\":[],\"owners\":[],\"writers\":[]}";
put = httpPut("/notebook/" + note2.getId() + "/permissions/", jsonRequest);
put.releaseConnection();
get = httpGet("/notebook/" + note2.getId() + "/permissions/");
assertThat(get, isAllowed());
resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
authInfo = (Map<String, Set<String>>) resp.get("body");
assertEquals(authInfo.get("readers"), Lists.newArrayList());
assertEquals(authInfo.get("writers"), Lists.newArrayList());
assertEquals(authInfo.get("owners"), Lists.newArrayList());
get.releaseConnection();
//cleanup
ZeppelinServer.notebook.removeNote(note1.getId());
ZeppelinServer.notebook.removeNote(note2.getId());
}
}

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.rest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -690,5 +691,70 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
ZeppelinServer.notebook.removeNote(note.getId());
}
@Test
public void testSearch() throws IOException {
Map<String, String> body;
GetMethod getSecurityTicket = httpGet("/security/ticket");
getSecurityTicket.addRequestHeader("Origin", "http://localhost");
Map<String, Object> respSecurityTicket = gson.fromJson(getSecurityTicket.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
body = (Map<String, String>) respSecurityTicket.get("body");
String username = body.get("principal");
getSecurityTicket.releaseConnection();
Note note1 = ZeppelinServer.notebook.createNote();
String jsonRequest = "{\"title\": \"title1\", \"text\": \"ThisIsToTestSearchMethodWithPermissions 1\"}";
PostMethod postNotebookText = httpPost("/notebook/" + note1.getId() + "/paragraph", jsonRequest);
postNotebookText.releaseConnection();
Note note2 = ZeppelinServer.notebook.createNote();
jsonRequest = "{\"title\": \"title1\", \"text\": \"ThisIsToTestSearchMethodWithPermissions 2\"}";
postNotebookText = httpPost("/notebook/" + note2.getId() + "/paragraph", jsonRequest);
postNotebookText.releaseConnection();
String jsonPermissions = "{\"owners\":[\"" + username + "\"],\"readers\":[\"" + username + "\"],\"writers\":[\"" + username + "\"]}";
PutMethod putPermission = httpPut("/notebook/" + note1.getId() + "/permissions", jsonPermissions);
putPermission.releaseConnection();
jsonPermissions = "{\"owners\":[\"admin\"],\"readers\":[\"admin\"],\"writers\":[\"admin\"]}";
putPermission = httpPut("/notebook/" + note2.getId() + "/permissions", jsonPermissions);
putPermission.releaseConnection();
GetMethod searchNotebook = httpGet("/notebook/search?q='ThisIsToTestSearchMethodWithPermissions'");
searchNotebook.addRequestHeader("Origin", "http://localhost");
Map<String, Object> respSearchResult = gson.fromJson(searchNotebook.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
ArrayList searchBody = (ArrayList) respSearchResult.get("body");
assertEquals("At-least one search results is there", true, searchBody.size() >= 1);
for (int i = 0; i < searchBody.size(); i++) {
Map<String, String> searchResult = (Map<String, String>) searchBody.get(i);
String userId = searchResult.get("id").split("/", 2)[0];
GetMethod getPermission = httpGet("/notebook/" + userId + "/permissions");
getPermission.addRequestHeader("Origin", "http://localhost");
Map<String, Object> resp = gson.fromJson(getPermission.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, ArrayList> permissions = (Map<String, ArrayList>) resp.get("body");
ArrayList owners = permissions.get("owners");
ArrayList readers = permissions.get("readers");
ArrayList writers = permissions.get("writers");
if (owners.size() != 0 && readers.size() != 0 && writers.size() != 0) {
assertEquals("User has permissions ", true, (owners.contains(username) || readers.contains(username) ||
writers.contains(username)));
}
getPermission.releaseConnection();
}
searchNotebook.releaseConnection();
ZeppelinServer.notebook.removeNote(note1.getId());
ZeppelinServer.notebook.removeNote(note2.getId());
}
}

View file

@ -95,7 +95,7 @@ public class NotebookServerTest extends AbstractTestRestApi {
List<InterpreterSetting> settings = note1.getNoteReplLoader().getInterpreterSettings();
for (InterpreterSetting setting : settings) {
if (setting.getName().equals("md")) {
interpreterGroup = setting.getInterpreterGroup();
interpreterGroup = setting.getInterpreterGroup("sharedProcess");
break;
}
}

View file

@ -354,6 +354,11 @@ module.exports = function (grunt) {
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: ['app/**/*.html', 'components/**/*.html']
}, {
expand: true,
cwd: 'bower_components/datatables/media/images',
src: '{,*/}*.{png,jpg,jpeg,gif}',
dest: '<%= yeoman.dist %>/images'
}, {
expand: true,
cwd: '.tmp/images',
@ -434,7 +439,7 @@ module.exports = function (grunt) {
]);
grunt.registerTask('build', [
'newer:jshint',
'jshint:all',
'clean:dist',
'wiredep',
'useminPrepare',

View file

@ -30,7 +30,9 @@
"ngtoast": "~2.0.0",
"ng-focus-if": "~1.0.2",
"bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
"floatThead": "~1.3.2"
"floatThead": "~1.3.2",
"datatables.net-bs": "~1.10.11",
"datatables.net-buttons-bs": "~1.1.2"
},
"devDependencies": {
"angular-mocks": "1.5.0"

View file

@ -124,7 +124,6 @@
</goals>
<configuration>
<arguments>build</arguments>
<arguments>--force</arguments>
</configuration>
</execution>

View file

@ -71,4 +71,9 @@ angular.module('zeppelinWebApp').controller('HomeCtrl', function($scope, noteboo
websocketMsgSrv.reloadAllNotesFromRepo();
$scope.isReloadingNotes = true;
};
$scope.toggleFolderNode = function(node) {
node.hidden = !node.hidden;
};
});

View file

@ -151,6 +151,70 @@ a.navbar-brand:hover {
font: normal normal normal 14px/1 FontAwesome;
}
.dropdown-submenu {
position: relative;
}
.dropdown-submenu a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.42857143;
color: #333;
white-space: nowrap;
text-decoration: none;
}
.dropdown-submenu a:hover {
background-color: #f5f5f5;
}
.dropdown-submenu > .dropdown-menu {
top:0;
left:100%;
margin-top:-6px;
margin-left:-1px;
-webkit-border-radius:0 6px 6px 6px;
-moz-border-radius:0 6px 6px 6px;
border-radius:0 6px 6px 6px;
}
.dropdown-submenu:hover > .dropdown-menu {
display:block;
}
/* overwrite the style of the first element of dropdown-menu */
.dropdown-submenu:hover > .dropdown-menu > li:first-child > a:hover {
color: #262626;
text-decoration: none;
background: #f5f5f5;
}
.dropdown-submenu > a:after {
display:block;
content:" ";
float:right;
width:0;
height:0;
border-color:transparent;
border-style:solid;
border-width:5px 0 5px 5px;
border-left-color:#cccccc;
margin-top:5px;
margin-right:-10px;
}
.dropdown-submenu:hover > a:after {
border-left-color:#ffffff;
}
.dropdown-submenu.pull-left {
float:none;
}
.dropdown-submenu.pull-left > .dropdown-menu {
left:-100%;
margin-left:10px;
-webkit-border-radius:6px 0 6px 6px;
-moz-border-radius:6px 0 6px 6px;
border-radius:6px 0 6px 6px;
}
@media (max-width: 767px) {
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
color: #D3D3D3;
@ -199,6 +263,7 @@ a.navbar-brand:hover {
#notebook-list {
position: relative;
overflow: hidden;
display: inline;
}
@media (min-width: 768px) {

View file

@ -12,6 +12,24 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/ng-template" id="notebook_folder_renderer.html">
<div ng-if="node.children == null">
<a style="text-decoration: none;" href="#/notebook/{{node.id}}">
<i style="font-size: 10px;" class="icon-doc"/> {{node.name || 'Note ' + node.id}}
</a>
</div>
<div ng-if="node.children != null">
<a style="text-decoration: none; cursor: pointer;" ng-click="toggleFolderNode(node)">
<i style="font-size: 10px;" ng-class="node.hidden ? 'icon-folder' : 'icon-folder-alt'" /> {{node.name}}
</a>
<div ng-if="!node.hidden">
<ul style="list-style-type: none; padding-left:15px;">
<li ng-repeat="node in node.children" ng-include="'notebook_folder_renderer.html'" />
</ul>
</div>
</div>
</script>
<div ng-controller="HomeCtrl as home">
<div ng-show="home.staticHome" class="box width-full home">
<div class="zeppelin">
@ -41,10 +59,15 @@ limitations under the License.
<i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
<ul id="notebook-names">
<li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
<li ng-repeat="note in home.notes.list | filter:query | orderBy:home.arrayOrderingSrv.notebookListOrdering track by $index">
<i style="font-size: 10px;" class="icon-doc"></i>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}}</a>
</li>
<div ng-if="!query || query.name === ''">
<li ng-repeat="node in home.notes.root.children" ng-include="'notebook_folder_renderer.html'" />
</div>
<div ng-if="query && query.name !== ''">
<li ng-repeat="note in home.notes.flatList | filter:query | orderBy:home.arrayOrderingSrv.notebookListOrdering track by $index">
<i style="font-size: 10px;" class="icon-doc"></i>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}}</a>
</li>
</div>
</ul>
</div>
</div>

View file

@ -36,9 +36,39 @@ limitations under the License.
</div>
<b>Option</b>
<div class="checkbox">
<label><input type="checkbox" style="top:-5px" ng-model="newInterpreterSetting.option.perNoteSession">Separate Interpreter for each note</input></label>
<div>
<span class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle"
data-toggle="dropdown">
{{getSessionOption(setting.id)}} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a style="cursor:pointer"
tooltip="Single interpreter instance are shared across notes"
ng-click="setSessionOption(setting.id, 'shared')">
shared
</a>
</li>
<li>
<a style="cursor:pointer"
tooltip="Separate Interpreter instance for each note"
ng-click="setSessionOption(setting.id, 'scoped')">
scoped
</a>
</li>
<li>
<a style="cursor:pointer"
tooltip="Separate Interpreter process for each note"
ng-click="setSessionOption(setting.id, 'isolated')">
isolated
</a>
</li>
</ul>
</span>
<span>Interpreter for note</span>
</div>
<br />
<b>Properties</b>
<table class="table table-striped properties">

View file

@ -60,44 +60,89 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
interpreterSettingsTmp[index] = angular.copy($scope.interpreterSettings[index]);
};
$scope.updateInterpreterSetting = function(form, settingId) {
var result = confirm('Do you want to update this interpreter and restart with new settings?');
if (result) {
$scope.setSessionOption = function(settingId, sessionOption) {
var option;
if (settingId === undefined) {
option = $scope.newInterpreterSetting.option;
} else {
var index = _.findIndex($scope.interpreterSettings, {'id': settingId});
var setting = $scope.interpreterSettings[index];
if (setting.propertyKey !== '' || setting.propertyKey) {
$scope.addNewInterpreterProperty(settingId);
}
if (setting.depArtifact !== '' || setting.depArtifact) {
$scope.addNewInterpreterDependency(settingId);
}
// add missing field of option
if (!setting.option) {
setting.option = {};
}
if (setting.option.remote === undefined) {
// remote always true for now
setting.option.remote = true;
}
var request = {
option: angular.copy(setting.option),
properties: angular.copy(setting.properties),
dependencies: angular.copy(setting.dependencies)
};
$http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/' + settingId, request).
success(function (data, status, headers, config) {
$scope.interpreterSettings[index] = data.body;
removeTMPSettings(index);
}).
error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message);
ngToast.danger({content: data.message, verticalPosition: 'bottom'});
form.$show();
});
option = setting.option;
}
if (sessionOption === 'isolated') {
option.perNoteSession = false;
option.perNoteProcess = true;
} else if (sessionOption === 'scoped') {
option.perNoteSession = true;
option.perNoteProcess = false;
} else {
option.perNoteSession = false;
option.perNoteProcess = false;
}
};
$scope.getSessionOption = function(settingId) {
var option;
if (settingId === undefined) {
option = $scope.newInterpreterSetting.option;
} else {
var index = _.findIndex($scope.interpreterSettings, {'id': settingId});
var setting = $scope.interpreterSettings[index];
option = setting.option;
}
if (option.perNoteSession) {
return 'scoped';
} else if (option.perNoteProcess) {
return 'isolated';
} else {
return 'shared';
}
};
$scope.updateInterpreterSetting = function(form, settingId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to update this interpreter and restart with new settings?',
callback: function (result) {
if (result) {
var index = _.findIndex($scope.interpreterSettings, {'id': settingId});
var setting = $scope.interpreterSettings[index];
if (setting.propertyKey !== '' || setting.propertyKey) {
$scope.addNewInterpreterProperty(settingId);
}
if (setting.depArtifact !== '' || setting.depArtifact) {
$scope.addNewInterpreterDependency(settingId);
}
// add missing field of option
if (!setting.option) {
setting.option = {};
}
if (setting.option.remote === undefined) {
// remote always true for now
setting.option.remote = true;
}
var request = {
option: angular.copy(setting.option),
properties: angular.copy(setting.properties),
dependencies: angular.copy(setting.dependencies)
};
$http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/' + settingId, request).
success(function (data, status, headers, config) {
$scope.interpreterSettings[index] = data.body;
removeTMPSettings(index);
}).
error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message);
ngToast.danger({content: data.message, verticalPosition: 'bottom'});
form.$show();
});
}
}
});
};
$scope.resetInterpreterSetting = function(settingId){
@ -227,7 +272,8 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
dependencies: [],
option: {
remote: true,
perNoteSession: false
perNoteSession: false,
perNoteProcess: false
}
};
emptyNewProperty($scope.newInterpreterSetting);

View file

@ -111,16 +111,39 @@ limitations under the License.
<div class="row interpreter">
<div class="col-md-12">
<h5>Option</h5>
<div class="checkbox">
<label>
<input type="checkbox"
style="top:-5px"
ng-disabled="!valueform.$visible"
ng-model="setting.option.perNoteSession">
Separate Interpreter for each note</input>
</label>
</div>
<span class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle"
data-toggle="dropdown"
ng-disabled="!valueform.$visible">
{{getSessionOption(setting.id)}} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a style="cursor:pointer"
tooltip="Single interpreter instance are shared across notes"
ng-click="setSessionOption(setting.id, 'shared')">
shared
</a>
</li>
<li>
<a style="cursor:pointer"
tooltip="Separate Interpreter instance for each note"
ng-click="setSessionOption(setting.id, 'scoped')">
scoped
</a>
</li>
<li>
<a style="cursor:pointer"
tooltip="Separate Interpreter process for each note"
ng-click="setSessionOption(setting.id, 'isolated')">
isolated
</a>
</li>
</ul>
</span>
<span>Interpreter for note</span>
</div>
<div ng-show="_.isEmpty(setting.properties) && _.isEmpty(setting.dependencies) || valueform.$hidden" class="col-md-12 gray40-message">
<em>Currently there are no properties and dependencies set for this interpreter</em>

View file

@ -13,6 +13,7 @@ limitations under the License.
-->
<div
id="p{{paragraph.id}}_resize"
style='padding-bottom: 5px;'
resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": "{{getResultType()}}"}'
resizable on-resize="resizeParagraph(width, height);">
<div ng-include src="'app/notebook/paragraph/paragraph-graph.html'"></div>

View file

@ -386,6 +386,7 @@ angular.module('zeppelinWebApp')
}
/** push the rest */
$scope.paragraph.authenticationInfo = data.paragraph.authenticationInfo;
$scope.paragraph.aborted = data.paragraph.aborted;
$scope.paragraph.dateUpdated = data.paragraph.dateUpdated;
$scope.paragraph.dateCreated = data.paragraph.dateCreated;
@ -967,7 +968,12 @@ angular.module('zeppelinWebApp')
}
return '';
}
var desc = 'Took ' + (timeMs/1000) + ' seconds';
var user = 'anonymous';
if (pdata.authenticationInfo !== null && pdata.authenticationInfo.user !== null) {
user = pdata.authenticationInfo.user;
}
var dateUpdated = (pdata.dateUpdated === null) ? 'unknown' : pdata.dateUpdated;
var desc = 'Took ' + (timeMs/1000) + ' seconds. Last updated by ' + user + ' at time ' + dateUpdated + '.';
if ($scope.isResultOutdated()){
desc += ' (outdated)';
}
@ -1265,40 +1271,55 @@ angular.module('zeppelinWebApp')
return '&#'+i.charCodeAt(0)+';';
});
}
html += ' <td>'+formatTableContent(v)+'</td>';
html += ' <td>'+formatTableContent(v)+'</td>';
}
html += ' </tr>';
}
html += ' </tbody>';
html += '</table>';
angular.element('#p' + $scope.paragraph.id + '_table').html(html);
var tableDomEl = angular.element('#p' + $scope.paragraph.id + '_table');
tableDomEl.html(html);
var oTable = tableDomEl.children(1).DataTable({
paging: false,
info: false,
autoWidth: false,
lengthChange: false,
searching: false,
dom: '<>'
});
if ($scope.paragraph.result.msgTable.length > 10000) {
angular.element('#p' + $scope.paragraph.id + '_table').css('overflow', 'scroll');
// set table height
var height = $scope.paragraph.config.graph.height;
angular.element('#p' + $scope.paragraph.id + '_table').css('height', height);
tableDomEl.css({
'overflow': 'scroll',
'height': $scope.paragraph.config.graph.height
});
} else {
var dataTable = angular.element('#p' + $scope.paragraph.id + '_table .table');
dataTable.floatThead({
scrollContainer: function (dataTable) {
return angular.element('#p' + $scope.paragraph.id + '_table');
scrollContainer: function(dataTable) {
return tableDomEl;
}
});
angular.element('#p' + $scope.paragraph.id + '_table .table').on('remove', function () {
angular.element('#p' + $scope.paragraph.id + '_table .table').floatThead('destroy');
dataTable.on('remove', function () {
dataTable.floatThead('destroy');
});
angular.element('#p' + $scope.paragraph.id + '_table').css('position', 'relative');
angular.element('#p' + $scope.paragraph.id + '_table').css('height', '100%');
angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar('destroy');
angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar();
tableDomEl.css({
'position': 'relative',
'height': '100%'
});
tableDomEl.perfectScrollbar('destroy')
.perfectScrollbar({minScrollbarLength: 20});
angular.element('.ps-scrollbar-y-rail').css('z-index', '1002');
// set table height
var psHeight = $scope.paragraph.config.graph.height;
angular.element('#p' + $scope.paragraph.id + '_table').css('height', psHeight);
angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar('update');
tableDomEl.css('height', psHeight);
tableDomEl.perfectScrollbar('update');
}
};

View file

@ -52,6 +52,21 @@
width: 100%;
}
table.dataTable {
margin-top: 0px !important;
margin-bottom: 6px !important;
}
table.dataTable.table-condensed > thead > tr > th {
padding-right: 28px;
}
table.dataTable.table-condensed .sorting:after,
table.dataTable.table-condensed .sorting_asc:after,
table.dataTable.table-condensed .sorting_desc:after {
right: 12px;
}
.graphContainer {
position: relative;
margin-bottom: 5px;

View file

@ -10,7 +10,19 @@ 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.
-->
-->
<script type="text/ng-template" id="notebook_list_renderer.html">
<a ng-if="note.id" href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}} </a>
<li ng-if="!note.id"
class="dropdown-submenu">
<a tabindex="-1" href="javascript: void(0)">{{note.name}}</a>
<ul class="dropdown-menu">
<li ng-repeat="note in note.children track by $index" ng-class="{'active' : navbar.isActive(note.id)}" ng-include="'notebook_list_renderer.html'">
</li>
</ul>
</li>
</script>
<div class="navbar navbar-inverse navbar-fixed-top" style="display: none;" role="navigation" ng-class="{'displayNavBar': !asIframe}">
<div class="container">
<div class="navbar-header">
@ -32,10 +44,7 @@ limitations under the License.
<li><a href="" data-toggle="modal" data-target="#noteNameModal"><i class="fa fa-plus"></i> Create new note</a></li>
<li class="divider"></li>
<div id="notebook-list" class="scrollbar-container">
<li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
<li ng-repeat="note in navbar.notes.list | filter:query | orderBy:navbar.arrayOrderingSrv.notebookListOrdering track by $index"
ng-class="{'active' : navbar.isActive(note.id)}">
<a href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}} </a>
<li ng-repeat="note in navbar.notes.root.children track by $index" ng-class="{'active' : navbar.isActive(note.id)}" ng-include="'notebook_list_renderer.html'">
</li>
</div>
</ul>

View file

@ -28,6 +28,7 @@ limitations under the License.
placeholder="Note name" type="text" class="form-control"
id="noteName" ng-model="note.notename" ng-enter="notenamectrl.handleNameEnter()">
</div>
Use '/' to create folders. Example: /NoteDirA/Notebook1
</div>
<div class="modal-footer">
<button type="button" id="createNoteButton"

View file

@ -14,13 +14,52 @@
'use strict';
angular.module('zeppelinWebApp').factory('notebookListDataFactory', function() {
var notes = {};
notes.list = [];
var notes = {
root: {children: []},
flatList: [],
notes.setNotes = function(notesList) {
notes.list = angular.copy(notesList);
setNotes: function(notesList) {
// a flat list to boost searching
notes.flatList = angular.copy(notesList);
// construct the folder-based tree
notes.root = {children: []};
_.reduce(notesList, function(root, note) {
var noteName = note.name || note.id;
var nodes = noteName.match(/([^\\\][^\/]|\\\/)+/g);
// recursively add nodes
addNode(root, nodes, note.id);
return root;
}, notes.root);
}
};
var addNode = function(curDir, nodes, noteId) {
if (nodes.length === 1) { // the leaf
curDir.children.push({
name : nodes[0],
id : noteId
});
} else { // a folder node
var node = nodes.shift();
var dir = _.find(curDir.children,
function(c) {return c.name === node && c.children !== undefined;});
if (dir !== undefined) { // found an existing dir
addNode(dir, nodes, noteId);
} else {
var newDir = {
name : node,
hidden : true,
children : []
};
curDir.children.push(newDir);
addNode(newDir, nodes, noteId);
}
}
};
return notes;
});
});

View file

@ -44,6 +44,8 @@ limitations under the License.
<link rel="stylesheet" href="bower_components/highlightjs/styles/github.css" />
<link rel="stylesheet" href="bower_components/ngtoast/dist/ngToast.css" />
<link rel="stylesheet" href="bower_components/bootstrap3-dialog/dist/css/bootstrap-dialog.min.css" />
<link rel="stylesheet" href="bower_components/datatables.net-bs/css/dataTables.bootstrap.css" />
<link rel="stylesheet" href="bower_components/datatables.net-buttons-bs/css/buttons.bootstrap.css" />
<!-- endbower -->
<link rel="stylesheet" href="bower_components/jquery-ui/themes/base/all.css" />
<!-- endbuild -->
@ -105,6 +107,7 @@ limitations under the License.
<script src="bower_components/angular-websocket/angular-websocket.min.js"></script>
<script src="bower_components/ace-builds/src-noconflict/ace.js"></script>
<script src="bower_components/ace-builds/src-noconflict/mode-scala.js"></script>
<script src="bower_components/ace-builds/src-noconflict/mode-python.js"></script>
<script src="bower_components/ace-builds/src-noconflict/mode-sql.js"></script>
<script src="bower_components/ace-builds/src-noconflict/mode-markdown.js"></script>
<script src="bower_components/ace-builds/src-noconflict/mode-sh.js"></script>
@ -131,6 +134,14 @@ limitations under the License.
<script src="bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js"></script>
<script src="bower_components/floatThead/dist/jquery.floatThead.js"></script>
<script src="bower_components/floatThead/dist/jquery.floatThead.min.js"></script>
<script src="bower_components/datatables.net/js/jquery.dataTables.js"></script>
<script src="bower_components/datatables.net-bs/js/dataTables.bootstrap.js"></script>
<script src="bower_components/datatables.net-buttons/js/dataTables.buttons.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.colVis.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.flash.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.html5.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.print.js"></script>
<script src="bower_components/datatables.net-buttons-bs/js/buttons.bootstrap.js"></script>
<!-- endbower -->
<!-- endbuild -->
<!-- build:js({.tmp,src}) scripts/scripts.js -->

View file

@ -34,6 +34,7 @@ module.exports = function(config) {
'bower_components/angular-websocket/angular-websocket.min.js',
'bower_components/ace-builds/src-noconflict/ace.js',
'bower_components/ace-builds/src-noconflict/mode-scala.js',
'bower_components/ace-builds/src-noconflict/mode-python.js',
'bower_components/ace-builds/src-noconflict/mode-sql.js',
'bower_components/ace-builds/src-noconflict/mode-markdown.js',
'bower_components/ace-builds/src-noconflict/mode-sh.js',

View file

@ -0,0 +1,69 @@
'use strict';
describe('Factory: NotebookList', function() {
var notebookList;
beforeEach(function () {
module('zeppelinWebApp');
inject(function ($injector) {
notebookList = $injector.get('notebookListDataFactory');
});
});
it('should generate both flat list and folder-based list properly', function() {
var notesList = [
{name: 'A', id: '000001'},
{name: 'B', id: '000002'},
{id: '000003'}, // notebook without name
{name: '/C/CA', id: '000004'},
{name: '/C/CB', id: '000005'},
{name: '/C/CB/CBA', id: '000006'}, // same name with a dir
{name: '/C/CB/CBA', id: '000007'}, // same name with another note
{name: 'C///CB//CBB', id: '000008'}
];
notebookList.setNotes(notesList);
var flatList = notebookList.flatList;
expect(flatList.length).toBe(8);
expect(flatList[0].name).toBe('A');
expect(flatList[0].id).toBe('000001');
expect(flatList[1].name).toBe('B');
expect(flatList[2].name).toBeUndefined();
expect(flatList[3].name).toBe('/C/CA');
expect(flatList[4].name).toBe('/C/CB');
expect(flatList[5].name).toBe('/C/CB/CBA');
expect(flatList[6].name).toBe('/C/CB/CBA');
expect(flatList[7].name).toBe('C///CB//CBB');
var folderList = notebookList.root.children;
expect(folderList.length).toBe(4);
expect(folderList[0].name).toBe('A');
expect(folderList[0].id).toBe('000001');
expect(folderList[1].name).toBe('B');
expect(folderList[2].name).toBe('000003');
expect(folderList[3].name).toBe('C');
expect(folderList[3].id).toBeUndefined();
expect(folderList[3].children.length).toBe(3);
expect(folderList[3].children[0].name).toBe('CA');
expect(folderList[3].children[0].id).toBe('000004');
expect(folderList[3].children[0].children).toBeUndefined();
expect(folderList[3].children[1].name).toBe('CB');
expect(folderList[3].children[1].id).toBe('000005');
expect(folderList[3].children[1].children).toBeUndefined();
expect(folderList[3].children[2].name).toBe('CB');
expect(folderList[3].children[2].id).toBeUndefined();
expect(folderList[3].children[2].children.length).toBe(3);
expect(folderList[3].children[2].children[0].name).toBe('CBA');
expect(folderList[3].children[2].children[0].id).toBe('000006');
expect(folderList[3].children[2].children[0].children).toBeUndefined();
expect(folderList[3].children[2].children[1].name).toBe('CBA');
expect(folderList[3].children[2].children[1].id).toBe('000007');
expect(folderList[3].children[2].children[1].children).toBeUndefined();
expect(folderList[3].children[2].children[2].name).toBe('CBB');
expect(folderList[3].children[2].children[2].id).toBe('000008');
expect(folderList[3].children[2].children[2].children).toBeUndefined();
});
});

View file

@ -33,6 +33,7 @@ import org.apache.zeppelin.interpreter.Interpreter.RegisteredInterpreter;
import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
import org.apache.zeppelin.interpreter.remote.RemoteInterpreter;
import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener;
import org.apache.zeppelin.notebook.NoteInterpreterLoader;
import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.Job.Status;
import org.slf4j.Logger;
@ -52,7 +53,7 @@ import java.util.*;
/**
* Manage interpreters.
*/
public class InterpreterFactory {
public class InterpreterFactory implements InterpreterGroupFactory {
Logger logger = LoggerFactory.getLogger(InterpreterFactory.class);
private Map<String, URLClassLoader> cleanCl = Collections
@ -244,9 +245,7 @@ public class InterpreterFactory {
setting.getDependencies(),
setting.getOption());
InterpreterGroup interpreterGroup = createInterpreterGroup(setting.id(), setting.getOption());
intpSetting.setInterpreterGroup(interpreterGroup);
intpSetting.setInterpreterGroupFactory(this);
interpreterSettings.put(k, intpSetting);
}
@ -370,7 +369,7 @@ public class InterpreterFactory {
* @throws InterpreterException
* @throws IOException
*/
public InterpreterGroup add(String name, String groupName,
public InterpreterSetting add(String name, String groupName,
List<Dependency> dependencies,
InterpreterOption option, Properties properties)
throws InterpreterException, IOException, RepositoryException {
@ -404,17 +403,15 @@ public class InterpreterFactory {
loadInterpreterDependencies(intpSetting);
}
InterpreterGroup interpreterGroup = createInterpreterGroup(intpSetting.id(), option);
intpSetting.setInterpreterGroup(interpreterGroup);
intpSetting.setInterpreterGroupFactory(this);
interpreterSettings.put(intpSetting.id(), intpSetting);
saveToFile();
return interpreterGroup;
return intpSetting;
}
}
private InterpreterGroup createInterpreterGroup(String id, InterpreterOption option)
@Override
public InterpreterGroup createInterpreterGroup(String id, InterpreterOption option)
throws InterpreterException, NullArgumentException {
//When called from REST API without option we receive NPE
@ -444,25 +441,28 @@ public class InterpreterFactory {
public void removeInterpretersForNote(InterpreterSetting interpreterSetting,
String noteId) {
if (!interpreterSetting.getOption().isPerNoteSession()) {
return;
if (interpreterSetting.getOption().isPerNoteProcess()) {
interpreterSetting.closeAndRemoveInterpreterGroup(noteId);
} else if (interpreterSetting.getOption().isPerNoteSession()) {
InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup(noteId);
interpreterGroup.close(noteId);
interpreterGroup.destroy(noteId);
synchronized (interpreterGroup) {
interpreterGroup.remove(noteId);
interpreterGroup.notifyAll(); // notify createInterpreterForNote()
}
logger.info("Interpreter instance {} for note {} is removed",
interpreterSetting.getName(),
noteId);
}
InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup();
interpreterGroup.close(noteId);
interpreterGroup.destroy(noteId);
synchronized (interpreterGroup) {
interpreterGroup.remove(noteId);
interpreterGroup.notifyAll(); // notify createInterpreterForNote()
}
logger.info("Interpreter instance {} for note {} is removed",
interpreterSetting.getName(),
noteId);
}
public void createInterpretersForNote(
InterpreterSetting interpreterSetting,
String noteId) {
InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup();
String noteId,
String key) {
InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup(noteId);
String groupName = interpreterSetting.getGroup();
InterpreterOption option = interpreterSetting.getOption();
Properties properties = interpreterSetting.getProperties();
@ -473,10 +473,12 @@ public class InterpreterFactory {
// interpreter process supposed to be terminated by RemoteInterpreterProcess.dereference()
// in ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT msec. However, if termination of the process and
// removal from interpreter group take too long, throw an error.
long minTimeout = 10 * 1000 * 1000000; // 10 sec
long minTimeout = 10L * 1000 * 1000000; // 10 sec
long interpreterRemovalWaitTimeout =
Math.max(minTimeout, conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT) * 2);
while (interpreterGroup.containsKey(noteId)) {
Math.max(
minTimeout,
conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT) * 1000000L * 2);
while (interpreterGroup.containsKey(key)) {
if (System.nanoTime() - interpreterRemovalWaitStart > interpreterRemovalWaitTimeout) {
throw new InterpreterException("Can not create interpreter");
}
@ -500,10 +502,10 @@ public class InterpreterFactory {
if (option.isRemote()) {
intp = createRemoteRepl(info.getPath(),
noteId,
key,
info.getClassName(),
properties,
interpreterGroup.id);
interpreterSetting.id());
} else {
intp = createRepl(info.getPath(),
info.getClassName(),
@ -511,10 +513,10 @@ public class InterpreterFactory {
}
synchronized (interpreterGroup) {
List<Interpreter> interpreters = interpreterGroup.get(noteId);
List<Interpreter> interpreters = interpreterGroup.get(key);
if (interpreters == null) {
interpreters = new LinkedList<Interpreter>();
interpreterGroup.put(noteId, interpreters);
interpreterGroup.put(key, interpreters);
}
interpreters.add(intp);
}
@ -532,8 +534,7 @@ public class InterpreterFactory {
synchronized (interpreterSettings) {
if (interpreterSettings.containsKey(id)) {
InterpreterSetting intp = interpreterSettings.get(id);
intp.getInterpreterGroup().close();
intp.getInterpreterGroup().destroy();
intp.closeAndRmoveAllInterpreterGroups();
interpreterSettings.remove(id);
for (List<String> settings : interpreterBindings.values()) {
@ -663,16 +664,12 @@ public class InterpreterFactory {
stopJobAllInterpreter(intpsetting);
intpsetting.getInterpreterGroup().close();
intpsetting.getInterpreterGroup().destroy();
intpsetting.closeAndRmoveAllInterpreterGroups();
intpsetting.setOption(option);
intpsetting.setProperties(properties);
intpsetting.setDependencies(dependencies);
InterpreterGroup interpreterGroup = createInterpreterGroup(intpsetting.id(), option);
intpsetting.setInterpreterGroup(interpreterGroup);
loadInterpreterDependencies(intpsetting);
saveToFile();
} else {
@ -689,13 +686,8 @@ public class InterpreterFactory {
stopJobAllInterpreter(intpsetting);
intpsetting.getInterpreterGroup().close();
intpsetting.getInterpreterGroup().destroy();
intpsetting.closeAndRmoveAllInterpreterGroups();
InterpreterGroup interpreterGroup = createInterpreterGroup(
intpsetting.id(),
intpsetting.getOption());
intpsetting.setInterpreterGroup(interpreterGroup);
} else {
throw new InterpreterException("Interpreter setting id " + id
+ " not found");
@ -705,17 +697,19 @@ public class InterpreterFactory {
private void stopJobAllInterpreter(InterpreterSetting intpsetting) {
if (intpsetting != null) {
for (List<Interpreter> interpreters : intpsetting.getInterpreterGroup().values()) {
for (Interpreter intp : interpreters) {
for (Job job : intp.getScheduler().getJobsRunning()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
}
for (Job job : intp.getScheduler().getJobsWaiting()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
for (InterpreterGroup intpGroup : intpsetting.getAllInterpreterGroups()) {
for (List<Interpreter> interpreters : intpGroup.values()) {
for (Interpreter intp : interpreters) {
for (Job job : intp.getScheduler().getJobsRunning()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
}
for (Job job : intp.getScheduler().getJobsWaiting()) {
job.abort();
job.setStatus(Status.ABORT);
logger.info("Job " + job.getJobName() + " aborted ");
}
}
}
}
@ -729,8 +723,7 @@ public class InterpreterFactory {
for (final InterpreterSetting intpsetting : intpsettings) {
Thread t = new Thread() {
public void run() {
intpsetting.getInterpreterGroup().close();
intpsetting.getInterpreterGroup().destroy();
intpsetting.closeAndRmoveAllInterpreterGroups();
}
};
t.start();
@ -809,9 +802,9 @@ public class InterpreterFactory {
private Interpreter createRemoteRepl(String interpreterPath, String noteId, String className,
Properties property, String interpreterId) {
Properties property, String interpreterSettingId) {
int connectTimeout = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT);
String localRepoPath = conf.getInterpreterLocalRepoPath() + "/" + interpreterId;
String localRepoPath = conf.getInterpreterLocalRepoPath() + "/" + interpreterSettingId;
int maxPoolSize = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_MAX_POOL_SIZE);
LazyOpenInterpreter intp = new LazyOpenInterpreter(new RemoteInterpreter(
property, noteId, className, conf.getInterpreterRemoteRunnerPath(),

View file

@ -0,0 +1,26 @@
/*
* 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.interpreter;
import org.apache.commons.lang.NullArgumentException;
/**
* Created InterpreterGroup
*/
public interface InterpreterGroupFactory {
InterpreterGroup createInterpreterGroup(String interpreterGroupId, InterpreterOption option);
}

View file

@ -23,6 +23,7 @@ package org.apache.zeppelin.interpreter;
public class InterpreterOption {
boolean remote;
boolean perNoteSession;
boolean perNoteProcess;
public InterpreterOption() {
remote = false;
@ -47,4 +48,12 @@ public class InterpreterOption {
public void setPerNoteSession(boolean perNoteSession) {
this.perNoteSession = perNoteSession;
}
public boolean isPerNoteProcess() {
return perNoteProcess;
}
public void setPerNoteProcess(boolean perNoteProcess) {
this.perNoteProcess = perNoteProcess;
}
}

Some files were not shown because too many files have changed in this diff Show more