ZEPPELIN-612 Introduce APIs which shows list of configurations via WebSocket / REST API

This commit is contained in:
Jungtaek Lim 2016-01-16 21:00:59 +09:00
parent 0d157aebd2
commit 400de0245f
8 changed files with 357 additions and 5 deletions

View file

@ -0,0 +1,143 @@
---
layout: page
title: "Configuration REST API"
description: ""
group: rest-api
---
<!--
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 %}
## 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 view 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)
<br />
### Configuration REST API list
<table class="table-configuration">
<col width="200">
<tr>
<th>List configurations</th>
<th></th>
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method return all key/value pair of configurations on the server.
Note: For security reason, some pairs would not be shown.</td>
</tr>
<tr>
<td>URL</td>
<td>```http://[zeppelin-server]:[zeppelin-port]/api/configurations/all```</td>
</tr>
<tr>
<td>Success code</td>
<td>200</td>
</tr>
<tr>
<td> Fail code</td>
<td> 500 </td>
</tr>
<tr>
<td> sample JSON response
</td>
<td>
<pre>
{
"status":"OK",
"message":"",
"body":{
"zeppelin.war.tempdir":"webapps",
"zeppelin.notebook.homescreen.hide":"false",
"zeppelin.interpreter.remoterunner":"bin/interpreter.sh",
"zeppelin.notebook.s3.user":"user",
"zeppelin.server.port":"8089",
"zeppelin.dep.localrepo":"local-repo",
"zeppelin.ssl.truststore.type":"JKS",
"zeppelin.ssl.keystore.path":"keystore",
"zeppelin.notebook.s3.bucket":"zeppelin",
"zeppelin.server.addr":"0.0.0.0",
"zeppelin.ssl.client.auth":"false",
"zeppelin.server.context.path":"/",
"zeppelin.ssl.keystore.type":"JKS",
"zeppelin.ssl.truststore.path":"truststore",
"zeppelin.interpreters":"org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.phoenix.PhoenixInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter",
"zeppelin.ssl":"false",
"zeppelin.notebook.autoInterpreterBinding":"true",
"zeppelin.notebook.homescreen":"",
"zeppelin.notebook.storage":"org.apache.zeppelin.notebook.repo.VFSNotebookRepo",
"zeppelin.interpreter.connect.timeout":"30000",
"zeppelin.anonymous.allowed":"true",
"zeppelin.server.allowed.origins":"*",
"zeppelin.encoding":"UTF-8"
}
}
</pre>
</td>
</tr>
</table>
<br/>
<table class="table-configuration">
<col width="200">
<tr>
<th>List configurations (prefix match)</th>
<th></th>
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method return all prefix matched key/value pair of configurations on the server.
Note: For security reason, some pairs would not be shown.</td>
</tr>
<tr>
<td>URL</td>
<td>```http://[zeppelin-server]:[zeppelin-port]/api/configurations/prefix/[prefix]```</td>
</tr>
<tr>
<td>Success code</td>
<td>200</td>
</tr>
<tr>
<td> Fail code</td>
<td> 500 </td>
</tr>
<tr>
<td> sample JSON response
</td>
<td>
<pre>
{
"status":"OK",
"message":"",
"body":{
"zeppelin.ssl.keystore.type":"JKS",
"zeppelin.ssl.truststore.path":"truststore",
"zeppelin.ssl.truststore.type":"JKS",
"zeppelin.ssl.keystore.path":"keystore",
"zeppelin.ssl":"false",
"zeppelin.ssl.client.auth":"false"
}
}
</pre>
</td>
</tr>
</table>

View file

@ -0,0 +1,81 @@
/*
* 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 org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.server.JsonResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.util.Map;
/**
* Configurations Rest API Endpoint
*/
@Path("/configurations")
@Produces("application/json")
public class ConfigurationsRestApi {
private Notebook notebook;
public ConfigurationsRestApi() {}
public ConfigurationsRestApi(Notebook notebook) {
this.notebook = notebook;
}
@GET
@Path("all")
public Response getAll() {
ZeppelinConfiguration conf = notebook.getConf();
Map<String, String> configurations = conf.dumpConfigurations(conf,
new ZeppelinConfiguration.ConfigurationKeyPredicate() {
@Override
public boolean apply(String key) {
return !key.contains("password");
}
}
);
return new JsonResponse(Status.OK, "", configurations).build();
}
@GET
@Path("prefix/{prefix}")
public Response getByPrefix(@PathParam("prefix") final String prefix) {
ZeppelinConfiguration conf = notebook.getConf();
Map<String, String> configurations = conf.dumpConfigurations(conf,
new ZeppelinConfiguration.ConfigurationKeyPredicate() {
@Override
public boolean apply(String key) {
return !key.contains("password") && key.startsWith(prefix);
}
}
);
return new JsonResponse(Status.OK, "", configurations).build();
}
}

View file

@ -35,10 +35,7 @@ import org.apache.zeppelin.interpreter.InterpreterFactory;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.notebook.repo.NotebookRepoSync;
import org.apache.zeppelin.rest.InterpreterRestApi;
import org.apache.zeppelin.rest.NotebookRestApi;
import org.apache.zeppelin.rest.SecurityRestApi;
import org.apache.zeppelin.rest.ZeppelinRestApi;
import org.apache.zeppelin.rest.*;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.apache.zeppelin.search.SearchService;
import org.apache.zeppelin.search.LuceneSearch;
@ -288,6 +285,9 @@ public class ZeppelinServer extends Application {
SecurityRestApi securityApi = new SecurityRestApi();
singletons.add(securityApi);
ConfigurationsRestApi settingsApi = new ConfigurationsRestApi(notebook);
singletons.add(settingsApi);
return singletons;
}
}

View file

@ -100,7 +100,11 @@ public class Message {
ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object
ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del
ANGULAR_OBJECT_UPDATED // [c-s] angular object value updated
ANGULAR_OBJECT_UPDATED, // [c-s] angular object value updated,
LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations
CONFIGURATIONS_INFO // [s-c] all key/value pairs of configurations
// @param settings serialized Map<String, String> object
}
public OP op;

View file

@ -168,6 +168,9 @@ public class NotebookServer extends WebSocketServlet implements
case ANGULAR_OBJECT_UPDATED:
angularObjectUpdated(conn, notebook, messagereceived);
break;
case LIST_CONFIGURATIONS:
sendAllConfigurations(conn, notebook);
break;
default:
broadcastNoteList();
break;
@ -748,6 +751,22 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void sendAllConfigurations(NotebookSocket conn, Notebook notebook)
throws IOException {
ZeppelinConfiguration conf = notebook.getConf();
Map<String, String> configurations = conf.dumpConfigurations(conf,
new ZeppelinConfiguration.ConfigurationKeyPredicate() {
@Override
public boolean apply(String key) {
return !key.contains("password");
}
});
conn.send(serializeMessage(new Message(OP.CONFIGURATIONS_INFO)
.put("configurations", configurations)));
}
/**
* This callback is for the paragraph that runs on ZeppelinServer
* @param noteId

View file

@ -0,0 +1,63 @@
package org.apache.zeppelin.rest;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.httpclient.methods.GetMethod;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import static org.junit.Assert.assertTrue;
public class ConfigurationsRestApiTest 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 testGetAll() throws IOException {
GetMethod get = httpGet("/configurations/all");
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>(){}.getType());
Map<String, String> body = (Map<String, String>) resp.get("body");
assertTrue(body.size() > 0);
// it shouldn't have key/value pair which key contains "password"
assertTrue(Iterators.all(body.keySet().iterator(), new Predicate<String>() {
@Override
public boolean apply(String key) {
return !key.contains("password");
}
}
));
}
@Test
public void testGetViaPrefix() throws IOException {
final String prefix = "zeppelin.server";
GetMethod get = httpGet("/configurations/prefix/" + prefix);
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>(){}.getType());
Map<String, String> body = (Map<String, String>) resp.get("body");
assertTrue(body.size() > 0);
assertTrue(Iterators.all(body.keySet().iterator(), new Predicate<String>() {
@Override
public boolean apply(String key) {
return !key.contains("password") && key.startsWith(prefix);
}
}
));
}
}

View file

@ -0,0 +1,2 @@
package org.apache.zeppelin.conf;

View file

@ -19,7 +19,9 @@ package org.apache.zeppelin.conf;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
@ -373,6 +375,44 @@ public class ZeppelinConfiguration extends XMLConfiguration {
return Arrays.asList(getString(ConfVars.ZEPPELIN_ALLOWED_ORIGINS).toLowerCase().split(","));
}
public Map<String, String> dumpConfigurations(ZeppelinConfiguration conf,
ConfigurationKeyPredicate predicate) {
Map<String, String> configurations = new HashMap<>();
for (ZeppelinConfiguration.ConfVars v : ZeppelinConfiguration.ConfVars.values()) {
String key = v.getVarName();
if (!predicate.apply(key)) {
continue;
}
ConfVars.VarType type = v.getType();
Object value = null;
if (type == ConfVars.VarType.BOOLEAN) {
value = conf.getBoolean(v);
} else if (type == ConfVars.VarType.LONG) {
value = conf.getLong(v);
} else if (type == ConfVars.VarType.INT) {
value = conf.getInt(v);
} else if (type == ConfVars.VarType.FLOAT) {
value = conf.getFloat(v);
} else if (type == ConfVars.VarType.STRING) {
value = conf.getString(v);
}
if (value != null) {
configurations.put(key, value.toString());
}
}
return configurations;
}
/**
* Predication whether key/value pair should be included or not
*/
public interface ConfigurationKeyPredicate {
boolean apply(String key);
}
/**
* Wrapper class.