diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AuthenticatedCronRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AuthenticatedCronRestApiTest.java new file mode 100644 index 00000000000..3aff15621fc --- /dev/null +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AuthenticatedCronRestApiTest.java @@ -0,0 +1,181 @@ +/* + * 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.Map; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.zeppelin.MiniZeppelinServer; +import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; +import org.apache.zeppelin.notebook.Notebook; +import org.apache.zeppelin.notebook.Paragraph; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AuthenticatedCronRestApiTest extends AbstractTestRestApi { + private static final String ADMIN_USER = "admin"; + private static final String ADMIN_PASSWORD = "password1"; + private static final String VALID_CRON_REQUEST = "{\"cron\":\"0 0 0 1 1 ? 2099\"}"; + + private static MiniZeppelinServer zepServer; + private Notebook notebook; + private AuthenticationInfo admin; + + @BeforeAll + static void init() throws Exception { + zepServer = new MiniZeppelinServer(AuthenticatedCronRestApiTest.class.getSimpleName()); + zepServer.addConfigFile("shiro.ini", ZEPPELIN_SHIRO); + zepServer.addInterpreter("md"); + zepServer.getZeppelinConfiguration().setProperty( + ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "true"); + zepServer.getZeppelinConfiguration().setProperty( + ConfVars.ZEPPELIN_NOTEBOOK_CRON_FOLDERS.getVarName(), "/System"); + zepServer.start(); + } + + @AfterAll + static void destroy() throws Exception { + zepServer.destroy(); + } + + @BeforeEach + void setUp() { + zConf = zepServer.getZeppelinConfiguration(); + notebook = zepServer.getService(Notebook.class); + admin = new AuthenticationInfo(ADMIN_USER); + } + + @Test + void testCronForNonexistentNote() throws IOException { + try ( + CloseableHttpResponse response = + httpPost( + "/notebook/cron/notexistnote", + VALID_CRON_REQUEST, + ADMIN_USER, + ADMIN_PASSWORD)) { + assertThat("", response, isNotFound()); + } + } + + @Test + void testCronLifecycleInConfiguredFolder() throws Exception { + String noteId = null; + try { + assertTrue(zConf.isAuthenticationEnabled()); + assertTrue(zConf.isZeppelinNotebookCronEnable()); + noteId = notebook.createNote("/System/testCronLifecycleInConfiguredFolder", admin); + notebook.processNote(noteId, + note -> { + assertNotNull(note, "can't create new note"); + note.setName("testCronLifecycleInConfiguredFolder"); + Paragraph paragraph = note.addNewParagraph(admin); + Map config = paragraph.getConfig(); + config.put("enabled", true); + paragraph.setConfig(config); + paragraph.setText("%md This is test paragraph."); + notebook.saveNote(note, admin); + return null; + }); + + try ( + CloseableHttpResponse response = + httpPost( + "/notebook/cron/" + noteId, + VALID_CRON_REQUEST, + ADMIN_USER, + ADMIN_PASSWORD)) { + assertThat("", response, isAllowed()); + } + + try ( + CloseableHttpResponse response = + httpGet( + "/notebook/cron/" + noteId, + ADMIN_USER, + ADMIN_PASSWORD)) { + assertThat("", response, isAllowed()); + } + + String invalidCronRequest = "{\"cron\":\"a * * * * ?\"}"; + try ( + CloseableHttpResponse response = + httpPost( + "/notebook/cron/" + noteId, + invalidCronRequest, + ADMIN_USER, + ADMIN_PASSWORD)) { + assertThat("", response, isBadRequest()); + } + + try ( + CloseableHttpResponse response = + httpDelete( + "/notebook/cron/" + noteId, + ADMIN_USER, + ADMIN_PASSWORD)) { + assertThat("", response, isAllowed()); + } + } finally { + if (noteId != null) { + notebook.removeNote(noteId, admin); + } + } + } + + @Test + void testCronRejectedOutsideConfiguredFolder() throws Exception { + String noteId = null; + try { + noteId = notebook.createNote("/Other/testCronRejectedOutsideConfiguredFolder", admin); + notebook.processNote(noteId, + note -> { + assertNotNull(note, "can't create new note"); + note.setName("testCronRejectedOutsideConfiguredFolder"); + Paragraph paragraph = note.addNewParagraph(admin); + Map config = paragraph.getConfig(); + config.put("enabled", true); + paragraph.setConfig(config); + paragraph.setText("%md This is test paragraph."); + notebook.saveNote(note, admin); + return null; + }); + + try ( + CloseableHttpResponse response = + httpPost( + "/notebook/cron/" + noteId, + VALID_CRON_REQUEST, + ADMIN_USER, + ADMIN_PASSWORD)) { + assertThat("", response, isForbidden()); + } + } finally { + if (noteId != null) { + notebook.removeNote(noteId, admin); + } + } + } +} diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java index 5b3977c2ed4..385050ba77a 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java @@ -29,7 +29,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @@ -54,6 +53,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -78,6 +78,8 @@ public static void init() throws Exception { zepServer.addInterpreter("sh"); zepServer.addInterpreter("spark"); zepServer.copyBinDir(); + zepServer.getZeppelinConfiguration().setProperty( + ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "true"); zepServer.start(); TestHelper.configureSparkInterpreter(zepServer, sparkHome); } @@ -676,143 +678,43 @@ void testRunParagraphWithParams() throws Exception { } } - @Disabled // TODO(ZEPPELIN-5994): Fix and enable this test @Test - void testJobs() throws Exception { - // create a note and a paragraph + void testCronDisabledInAnonymousMode() throws Exception { String noteId = null; try { - System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "true"); - noteId = notebook.createNote("note1_testJobs", anonymous); - // Use write lock, because name is overwritten + assertFalse(zConf.isAuthenticationEnabled()); + assertFalse(zConf.isZeppelinNotebookCronEnable()); + noteId = notebook.createNote("note1_testCronDisabledInAnonymousMode", anonymous); notebook.processNote(noteId, - note -> { - note.setName("note for run test"); - Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS); - paragraph.setText("%md This is test paragraph."); - - Map config = paragraph.getConfig(); - config.put("enabled", true); - paragraph.setConfig(config); - return null; - }); - - notebook.processNote(noteId, - note -> { - try { - note.runAll(AuthenticationInfo.ANONYMOUS, false, false, new HashMap<>()); - } catch (Exception e) { - fail(); - } - return null; - }); + note -> { + assertNotNull(note, "can't create new note"); + note.setName("note for anonymous cron test"); + Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS); + Map config = paragraph.getConfig(); + config.put("enabled", true); + paragraph.setConfig(config); + paragraph.setText("%md This is test paragraph."); + notebook.saveNote(note, anonymous); + return null; + }); String jsonRequest = "{\"cron\":\"* * * * * ?\" }"; - // right cron expression but not exist note. - CloseableHttpResponse postCron = httpPost("/notebook/cron/notexistnote", jsonRequest); - assertThat("", postCron, isNotFound()); - postCron.close(); - - // right cron expression. - postCron = httpPost("/notebook/cron/" + noteId, jsonRequest); - assertThat("", postCron, isAllowed()); - postCron.close(); - Thread.sleep(1000); - - // wrong cron expression. - jsonRequest = "{\"cron\":\"a * * * * ?\" }"; - postCron = httpPost("/notebook/cron/" + noteId, jsonRequest); - assertThat("", postCron, isBadRequest()); - postCron.close(); - Thread.sleep(1000); - - // remove cron job. - CloseableHttpResponse deleteCron = httpDelete("/notebook/cron/" + noteId); - assertThat("", deleteCron, isAllowed()); - deleteCron.close(); - } finally { - //cleanup - if (null != noteId) { - notebook.removeNote(noteId, anonymous); + try ( + CloseableHttpResponse postCron = + httpPost( + "/notebook/cron/" + noteId, + jsonRequest)) { + assertThat("", postCron, isForbidden()); + } + try ( + CloseableHttpResponse deleteCron = + httpDelete("/notebook/cron/" + noteId)) { + assertThat("", deleteCron, isForbidden()); } - System.clearProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName()); - } - } - - @Disabled // TODO(ZEPPELIN-5994): Fix and enable this test - @Test - void testCronDisable() throws Exception { - String noteId = null; - try { - // create a note and a paragraph - System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "false"); - noteId = notebook.createNote("note1_testCronDisable", anonymous); - // use write lock because Name is overwritten - notebook.processNote(noteId, - note -> { - note.setName("note for run test"); - Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS); - paragraph.setText("%md This is test paragraph."); - - Map config = paragraph.getConfig(); - config.put("enabled", true); - paragraph.setConfig(config); - return null; - }); - - notebook.processNote(noteId, - note -> { - try { - note.runAll(AuthenticationInfo.ANONYMOUS, true, true, new HashMap<>()); - } catch (Exception e) { - fail(); - } - return null; - }); - - - String jsonRequest = "{\"cron\":\"* * * * * ?\" }"; - // right cron expression. - CloseableHttpResponse postCron = httpPost("/notebook/cron/" + noteId, jsonRequest); - assertThat("", postCron, isForbidden()); - postCron.close(); - - System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "true"); - System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_FOLDERS.getVarName(), "/System"); - - // use write lock, because Name is overwritten - notebook.processNote(noteId, - note -> { - note.setName("System/test2"); - return null; - }); - notebook.processNote(noteId, - note -> { - try { - note.runAll(AuthenticationInfo.ANONYMOUS, true, true, new HashMap<>()); - } catch (Exception e) { - fail(); - } - return null; - }); - postCron = httpPost("/notebook/cron/" + noteId, jsonRequest); - assertThat("", postCron, isAllowed()); - postCron.close(); - Thread.sleep(1000); - - // remove cron job. - CloseableHttpResponse deleteCron = httpDelete("/notebook/cron/" + noteId); - assertThat("", deleteCron, isAllowed()); - deleteCron.close(); - Thread.sleep(1000); - - System.clearProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_FOLDERS.getVarName()); } finally { - //cleanup if (null != noteId) { notebook.removeNote(noteId, anonymous); } - System.clearProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName()); } }