/* * 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 java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.zeppelin.interpreter.InterpreterOption; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.scheduler.Job.Status; import org.apache.zeppelin.server.ZeppelinServer; import org.apache.zeppelin.user.AuthenticationInfo; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import com.google.gson.Gson; import static org.junit.Assert.*; import static org.hamcrest.MatcherAssert.assertThat; /** * Zeppelin interpreter rest api tests */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class InterpreterRestApiTest extends AbstractTestRestApi { Gson gson = new Gson(); AuthenticationInfo anonymous; @BeforeClass public static void init() throws Exception { AbstractTestRestApi.startUp(); } @AfterClass public static void destroy() throws Exception { AbstractTestRestApi.shutDown(); } @Before public void setUp() { anonymous = new AuthenticationInfo("anonymous"); } @Test public void getAvailableInterpreters() throws IOException { // when GetMethod get = httpGet("/interpreter"); JsonObject body = getBodyFieldFromResponse(get.getResponseBodyAsString()); // then assertThat(get, isAllowed()); assertEquals(ZeppelinServer.notebook.getInterpreterSettingManager().getAvailableInterpreterSettings().size(), body.entrySet().size()); get.releaseConnection(); } @Test public void getSettings() throws IOException { // when GetMethod get = httpGet("/interpreter/setting"); // then assertThat(get, isAllowed()); // DO NOT REMOVE: implies that body is properly parsed as an array JsonArray body = getArrayBodyFieldFromResponse(get.getResponseBodyAsString()); get.releaseConnection(); } @Test public void testGetNonExistInterpreterSetting() throws IOException { // when String nonExistInterpreterSettingId = "apache_.zeppelin_1s_.aw3some$"; GetMethod get = httpGet("/interpreter/setting/" + nonExistInterpreterSettingId); // then assertThat("Test get method:", get, isNotFound()); get.releaseConnection(); } @Test public void testSettingsCRUD() throws IOException { // when: call create setting API String rawRequest = "{\"name\":\"md2\",\"group\":\"md\",\"properties\":{\"propname\":\"propvalue\"}," + "\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," + "\"dependencies\":[]," + "\"option\": { \"remote\": true, \"session\": false }}"; JsonObject jsonRequest = gson.fromJson(rawRequest, JsonElement.class).getAsJsonObject(); PostMethod post = httpPost("/interpreter/setting/", jsonRequest.toString()); String postResponse = post.getResponseBodyAsString(); LOG.info("testSettingCRUD create response\n" + post.getResponseBodyAsString()); InterpreterSetting created = convertResponseToInterpreterSetting(postResponse); String newSettingId = created.getId(); // then : call create setting API assertThat("test create method:", post, isAllowed()); post.releaseConnection(); // when: call read setting API GetMethod get = httpGet("/interpreter/setting/" + newSettingId); String getResponse = get.getResponseBodyAsString(); LOG.info("testSettingCRUD get response\n" + getResponse); InterpreterSetting previouslyCreated = convertResponseToInterpreterSetting(getResponse); // then : read Setting API assertThat("Test get method:", get, isAllowed()); assertEquals(newSettingId, previouslyCreated.getId()); get.releaseConnection(); // when: call update setting API jsonRequest.getAsJsonObject("properties").addProperty("propname2", "this is new prop"); PutMethod put = httpPut("/interpreter/setting/" + newSettingId, jsonRequest.toString()); LOG.info("testSettingCRUD update response\n" + put.getResponseBodyAsString()); // then: call update setting API assertThat("test update method:", put, isAllowed()); put.releaseConnection(); // when: call delete setting API DeleteMethod delete = httpDelete("/interpreter/setting/" + newSettingId); LOG.info("testSettingCRUD delete response\n" + delete.getResponseBodyAsString()); // then: call delete setting API assertThat("Test delete method:", delete, isAllowed()); delete.releaseConnection(); } @Test public void testCreatedInterpreterDependencies() throws IOException { // when: Create 2 interpreter settings `md1` and `md2` which have different dep. String md1Name = "md1"; String md2Name = "md2"; String md1Dep = "org.apache.drill.exec:drill-jdbc:jar:1.7.0"; String md2Dep = "org.apache.drill.exec:drill-jdbc:jar:1.6.0"; String reqBody1 = "{\"name\":\"" + md1Name + "\",\"group\":\"md\",\"properties\":{\"propname\":\"propvalue\"}," + "\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," + "\"dependencies\":[ {\n" + " \"groupArtifactVersion\": \"" + md1Dep + "\",\n" + " \"exclusions\":[]\n" + " }]," + "\"option\": { \"remote\": true, \"session\": false }}"; PostMethod post = httpPost("/interpreter/setting", reqBody1); assertThat("test create method:", post, isAllowed()); post.releaseConnection(); String reqBody2 = "{\"name\":\"" + md2Name + "\",\"group\":\"md\",\"properties\":{\"propname\":\"propvalue\"}," + "\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," + "\"dependencies\":[ {\n" + " \"groupArtifactVersion\": \"" + md2Dep + "\",\n" + " \"exclusions\":[]\n" + " }]," + "\"option\": { \"remote\": true, \"session\": false }}"; post = httpPost("/interpreter/setting", reqBody2); assertThat("test create method:", post, isAllowed()); post.releaseConnection(); // 1. Call settings API GetMethod get = httpGet("/interpreter/setting"); String rawResponse = get.getResponseBodyAsString(); get.releaseConnection(); // 2. Parsing to List<InterpreterSettings> JsonObject responseJson = gson.fromJson(rawResponse, JsonElement.class).getAsJsonObject(); JsonArray bodyArr = responseJson.getAsJsonArray("body"); List<InterpreterSetting> settings = new Gson().fromJson(bodyArr, new TypeToken<ArrayList<InterpreterSetting>>() { }.getType()); // 3. Filter interpreters out we have just created InterpreterSetting md1 = null; InterpreterSetting md2 = null; for (InterpreterSetting setting : settings) { if (md1Name.equals(setting.getName())) { md1 = setting; } else if (md2Name.equals(setting.getName())) { md2 = setting; } } // then: should get created interpreters which have different dependencies // 4. Validate each md interpreter has its own dependencies assertEquals(1, md1.getDependencies().size()); assertEquals(1, md2.getDependencies().size()); assertEquals(md1Dep, md1.getDependencies().get(0).getGroupArtifactVersion()); assertEquals(md2Dep, md2.getDependencies().get(0).getGroupArtifactVersion()); } @Test public void testSettingsCreateWithEmptyJson() throws IOException { // Call Create Setting REST API PostMethod post = httpPost("/interpreter/setting/", ""); LOG.info("testSettingCRUD create response\n" + post.getResponseBodyAsString()); assertThat("test create method:", post, isBadRequest()); post.releaseConnection(); } @Test public void testInterpreterAutoBinding() throws IOException { // when Note note = ZeppelinServer.notebook.createNote(anonymous); GetMethod get = httpGet("/notebook/interpreter/bind/" + note.getId()); assertThat(get, isAllowed()); get.addRequestHeader("Origin", "http://localhost"); JsonArray body = getArrayBodyFieldFromResponse(get.getResponseBodyAsString()); // then: check interpreter is binded assertTrue(0 < body.size()); get.releaseConnection(); ZeppelinServer.notebook.removeNote(note.getId(), anonymous); } @Test public void testInterpreterRestart() throws IOException, InterruptedException { // when: create new note Note note = ZeppelinServer.notebook.createNote(anonymous); note.addNewParagraph(AuthenticationInfo.ANONYMOUS); Paragraph p = note.getLastParagraph(); Map config = p.getConfig(); config.put("enabled", true); // when: run markdown paragraph p.setConfig(config); p.setText("%md markdown"); p.setAuthenticationInfo(anonymous); note.run(p.getId()); while (p.getStatus() != Status.FINISHED) { Thread.sleep(100); } assertEquals(p.getResult().message().get(0).getData(), getSimulatedMarkdownResult("markdown")); // when: restart interpreter for (InterpreterSetting setting : ZeppelinServer.notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId())) { if (setting.getName().equals("md")) { // call restart interpreter API PutMethod put = httpPut("/interpreter/setting/restart/" + setting.getId(), ""); assertThat("test interpreter restart:", put, isAllowed()); put.releaseConnection(); break; } } // when: run markdown paragraph, again p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS); p.setConfig(config); p.setText("%md markdown restarted"); p.setAuthenticationInfo(anonymous); note.run(p.getId()); while (p.getStatus() != Status.FINISHED) { Thread.sleep(100); } // then assertEquals(p.getResult().message().get(0).getData(), getSimulatedMarkdownResult("markdown restarted")); ZeppelinServer.notebook.removeNote(note.getId(), anonymous); } @Test public void testRestartInterpreterPerNote() throws IOException, InterruptedException { // when: create new note Note note = ZeppelinServer.notebook.createNote(anonymous); note.addNewParagraph(AuthenticationInfo.ANONYMOUS); Paragraph p = note.getLastParagraph(); Map config = p.getConfig(); config.put("enabled", true); // when: run markdown paragraph. p.setConfig(config); p.setText("%md markdown"); p.setAuthenticationInfo(anonymous); note.run(p.getId()); while (p.getStatus() != Status.FINISHED) { Thread.sleep(100); } assertEquals(p.getResult().message().get(0).getData(), getSimulatedMarkdownResult("markdown")); // when: get md interpreter InterpreterSetting mdIntpSetting = null; for (InterpreterSetting setting : ZeppelinServer.notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId())) { if (setting.getName().equals("md")) { mdIntpSetting = setting; break; } } String jsonRequest = "{\"noteId\":\"" + note.getId() + "\"}"; // Restart isolated mode of Interpreter for note. mdIntpSetting.getOption().setPerNote(InterpreterOption.ISOLATED); PutMethod put = httpPut("/interpreter/setting/restart/" + mdIntpSetting.getId(), jsonRequest); assertThat("isolated interpreter restart:", put, isAllowed()); put.releaseConnection(); // Restart scoped mode of Interpreter for note. mdIntpSetting.getOption().setPerNote(InterpreterOption.SCOPED); put = httpPut("/interpreter/setting/restart/" + mdIntpSetting.getId(), jsonRequest); assertThat("scoped interpreter restart:", put, isAllowed()); put.releaseConnection(); // Restart shared mode of Interpreter for note. mdIntpSetting.getOption().setPerNote(InterpreterOption.SHARED); put = httpPut("/interpreter/setting/restart/" + mdIntpSetting.getId(), jsonRequest); assertThat("shared interpreter restart:", put, isAllowed()); put.releaseConnection(); ZeppelinServer.notebook.removeNote(note.getId(), anonymous); } @Test public void testListRepository() throws IOException { GetMethod get = httpGet("/interpreter/repository"); assertThat(get, isAllowed()); get.releaseConnection(); } @Test public void testAddDeleteRepository() throws IOException { // Call create repository API String repoId = "securecentral"; String jsonRequest = "{\"id\":\"" + repoId + "\",\"url\":\"https://repo1.maven.org/maven2\",\"snapshot\":\"false\"}"; PostMethod post = httpPost("/interpreter/repository/", jsonRequest); assertThat("Test create method:", post, isAllowed()); post.releaseConnection(); // Call delete repository API DeleteMethod delete = httpDelete("/interpreter/repository/" + repoId); assertThat("Test delete method:", delete, isAllowed()); delete.releaseConnection(); } public JsonObject getBodyFieldFromResponse(String rawResponse) { JsonObject response = gson.fromJson(rawResponse, JsonElement.class).getAsJsonObject(); return response.getAsJsonObject("body"); } public JsonArray getArrayBodyFieldFromResponse(String rawResponse) { JsonObject response = gson.fromJson(rawResponse, JsonElement.class).getAsJsonObject(); return response.getAsJsonArray("body"); } public InterpreterSetting convertResponseToInterpreterSetting(String rawResponse) { return gson.fromJson(getBodyFieldFromResponse(rawResponse), InterpreterSetting.class); } public static String getSimulatedMarkdownResult(String markdown) { return String.format("<div class=\"markdown-body\">\n<p>%s</p>\n</div>", markdown); } }