/* * 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.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Pattern; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; 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.commons.httpclient.methods.RequestEntity; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.server.ZeppelinServer; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; public abstract class AbstractTestRestApi { protected static final Logger LOG = LoggerFactory.getLogger(AbstractTestRestApi.class); static final String restApiUrl = "/api"; static final String url = getUrlToTest(); protected static final boolean wasRunning = checkIfServerIsRunning(); static boolean pySpark = false; static boolean sparkR = false; static Gson gson = new Gson(); static boolean isRunningWithAuth = false; private static File shiroIni = null; private static String zeppelinShiro = "[users]\n" + "admin = password1, admin\n" + "user1 = password2, role1, role2\n" + "user2 = password3, role3\n" + "user3 = password4, role2\n" + "[main]\n" + "sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager\n" + "securityManager.sessionManager = $sessionManager\n" + "securityManager.sessionManager.globalSessionTimeout = 86400000\n" + "shiro.loginUrl = /api/login\n" + "[roles]\n" + "role1 = *\n" + "role2 = *\n" + "role3 = *\n" + "admin = *\n" + "[urls]\n" + "/api/version = anon\n" + "/** = authc"; private String getUrl(String path) { String url; if (System.getProperty("url") != null) { url = System.getProperty("url"); } else { url = "http://localhost:8080"; } url += restApiUrl; if (path != null) url += path; return url; } protected static String getUrlToTest() { String url = "http://localhost:8080" + restApiUrl; if (System.getProperty("url") != null) { url = System.getProperty("url"); } return url; } static ExecutorService executor; protected static final Runnable server = new Runnable() { @Override public void run() { try { ZeppelinServer.main(new String[] {""}); } catch (Exception e) { LOG.error("Exception in WebDriverManager while getWebDriver ", e); throw new RuntimeException(e); } } }; private static void start(boolean withAuth) throws Exception { if (!wasRunning) { System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_HOME.getVarName(), "../"); System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_WAR.getVarName(), "../zeppelin-web/dist"); // some test profile does not build zeppelin-web. // to prevent zeppelin starting up fail, create zeppelin-web/dist directory new File("../zeppelin-web/dist").mkdirs(); LOG.info("Staring test Zeppelin up..."); ZeppelinConfiguration conf = ZeppelinConfiguration.create(); if (withAuth) { isRunningWithAuth = true; // Set Anonymous session to false. System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED.getVarName(), "false"); // Create a shiro env test. shiroIni = new File("../conf/shiro.ini"); if (!shiroIni.exists()) { shiroIni.createNewFile(); } FileUtils.writeStringToFile(shiroIni, zeppelinShiro); } // exclude org.apache.zeppelin.rinterpreter.* for scala 2.11 test String interpreters = conf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETERS); String interpretersCompatibleWithScala211Test = null; for (String intp : interpreters.split(",")) { if (intp.startsWith("org.apache.zeppelin.rinterpreter")) { continue; } if (interpretersCompatibleWithScala211Test == null) { interpretersCompatibleWithScala211Test = intp; } else { interpretersCompatibleWithScala211Test += "," + intp; } } System.setProperty( ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETERS.getVarName(), interpretersCompatibleWithScala211Test); executor = Executors.newSingleThreadExecutor(); executor.submit(server); long s = System.currentTimeMillis(); boolean started = false; while (System.currentTimeMillis() - s < 1000 * 60 * 3) { // 3 minutes Thread.sleep(2000); started = checkIfServerIsRunning(); if (started == true) { break; } } if (started == false) { throw new RuntimeException("Can not start Zeppelin server"); } LOG.info("Test Zeppelin stared."); // assume first one is spark InterpreterSetting sparkIntpSetting = null; for(InterpreterSetting intpSetting : ZeppelinServer.notebook.getInterpreterSettingManager().get()) { if (intpSetting.getName().equals("spark")) { sparkIntpSetting = intpSetting; } } Properties sparkProperties = (Properties) sparkIntpSetting.getProperties(); // ci environment runs spark cluster for testing // so configure zeppelin use spark cluster if ("true".equals(System.getenv("CI"))) { // set spark master and other properties sparkProperties.setProperty("master", "local[2]"); sparkProperties.setProperty("spark.cores.max", "2"); sparkProperties.setProperty("zeppelin.spark.useHiveContext", "false"); // set spark home for pyspark sparkProperties.setProperty("spark.home", getSparkHome()); sparkIntpSetting.setProperties(sparkProperties); pySpark = true; sparkR = true; ZeppelinServer.notebook.getInterpreterSettingManager().restart(sparkIntpSetting.getId()); } else { String sparkHome = getSparkHome(); if (sparkHome != null) { if (System.getenv("SPARK_MASTER") != null) { sparkProperties.setProperty("master", System.getenv("SPARK_MASTER")); } else { sparkProperties.setProperty("master", "local[2]"); } sparkProperties.setProperty("spark.cores.max", "2"); // set spark home for pyspark sparkProperties.setProperty("spark.home", sparkHome); sparkProperties.setProperty("zeppelin.spark.useHiveContext", "false"); pySpark = true; sparkR = true; } ZeppelinServer.notebook.getInterpreterSettingManager().restart(sparkIntpSetting.getId()); } } } protected static void startUpWithAuthenticationEnable() throws Exception { start(true); } protected static void startUp() throws Exception { start(false); } private static String getHostname() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { LOG.error("Exception in WebDriverManager while getWebDriver ", e); return "localhost"; } } private static String getSparkHome() { String sparkHome = System.getenv("SPARK_HOME"); if (sparkHome != null) { return sparkHome; } sparkHome = getSparkHomeRecursively(new File(System.getProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_HOME.getVarName()))); System.out.println("SPARK HOME detected " + sparkHome); return sparkHome; } boolean isPyspark() { return pySpark; } boolean isSparkR() { return sparkR; } private static String getSparkHomeRecursively(File dir) { if (dir == null) return null; File files [] = dir.listFiles(); if (files == null) return null; File homeDetected = null; for (File f : files) { if (isActiveSparkHome(f)) { homeDetected = f; break; } } if (homeDetected != null) { return homeDetected.getAbsolutePath(); } else { return getSparkHomeRecursively(dir.getParentFile()); } } private static boolean isActiveSparkHome(File dir) { return dir.getName().matches("spark-[0-9\\.]+[A-Za-z-]*-bin-hadoop[0-9\\.]+"); } protected static void shutDown() throws Exception { if (!wasRunning) { // restart interpreter to stop all interpreter processes List<String> settingList = ZeppelinServer.notebook.getInterpreterSettingManager() .getDefaultInterpreterSettingList(); for (String setting : settingList) { ZeppelinServer.notebook.getInterpreterSettingManager().restart(setting); } if (shiroIni != null) { FileUtils.deleteQuietly(shiroIni); } LOG.info("Terminating test Zeppelin..."); ZeppelinServer.jettyWebServer.stop(); executor.shutdown(); long s = System.currentTimeMillis(); boolean started = true; while (System.currentTimeMillis() - s < 1000 * 60 * 3) { // 3 minutes Thread.sleep(2000); started = checkIfServerIsRunning(); if (started == false) { break; } } if (started == true) { throw new RuntimeException("Can not stop Zeppelin server"); } LOG.info("Test Zeppelin terminated."); System.clearProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETERS.getVarName()); if (isRunningWithAuth) { isRunningWithAuth = false; System .clearProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED.getVarName()); } } } protected static boolean checkIfServerIsRunning() { GetMethod request = null; boolean isRunning = true; try { request = httpGet("/version"); isRunning = request.getStatusCode() == 200; } catch (IOException e) { LOG.error("AbstractTestRestApi.checkIfServerIsRunning() fails .. ZeppelinServer is not running"); isRunning = false; } finally { if (request != null) { request.releaseConnection(); } } return isRunning; } protected static GetMethod httpGet(String path) throws IOException { return httpGet(path, StringUtils.EMPTY, StringUtils.EMPTY); } protected static GetMethod httpGet(String path, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); GetMethod getMethod = new GetMethod(url + path); getMethod.addRequestHeader("Origin", url); if (userAndPasswordAreNotBlank(user, pwd)) { getMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); } httpClient.executeMethod(getMethod); LOG.info("{} - {}", getMethod.getStatusCode(), getMethod.getStatusText()); return getMethod; } protected static DeleteMethod httpDelete(String path) throws IOException { return httpDelete(path, StringUtils.EMPTY, StringUtils.EMPTY); } protected static DeleteMethod httpDelete(String path, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); DeleteMethod deleteMethod = new DeleteMethod(url + path); deleteMethod.addRequestHeader("Origin", url); if (userAndPasswordAreNotBlank(user, pwd)) { deleteMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); } httpClient.executeMethod(deleteMethod); LOG.info("{} - {}", deleteMethod.getStatusCode(), deleteMethod.getStatusText()); return deleteMethod; } protected static PostMethod httpPost(String path, String body) throws IOException { return httpPost(path, body, StringUtils.EMPTY, StringUtils.EMPTY); } protected static PostMethod httpPost(String path, String request, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); PostMethod postMethod = new PostMethod(url + path); postMethod.setRequestBody(request); postMethod.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); if (userAndPasswordAreNotBlank(user, pwd)) { postMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); } httpClient.executeMethod(postMethod); LOG.info("{} - {}", postMethod.getStatusCode(), postMethod.getStatusText()); return postMethod; } protected static PutMethod httpPut(String path, String body) throws IOException { return httpPut(path, body, StringUtils.EMPTY, StringUtils.EMPTY); } protected static PutMethod httpPut(String path, String body, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); PutMethod putMethod = new PutMethod(url + path); putMethod.addRequestHeader("Origin", url); RequestEntity entity = new ByteArrayRequestEntity(body.getBytes("UTF-8")); putMethod.setRequestEntity(entity); if (userAndPasswordAreNotBlank(user, pwd)) { putMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); } httpClient.executeMethod(putMethod); LOG.info("{} - {}", putMethod.getStatusCode(), putMethod.getStatusText()); return putMethod; } private static String getCookie(String user, String password) throws IOException { HttpClient httpClient = new HttpClient(); PostMethod postMethod = new PostMethod(url + "/login"); postMethod.addRequestHeader("Origin", url); postMethod.setParameter("password", password); postMethod.setParameter("userName", user); httpClient.executeMethod(postMethod); LOG.info("{} - {}", postMethod.getStatusCode(), postMethod.getStatusText()); Pattern pattern = Pattern.compile("JSESSIONID=([a-zA-Z0-9-]*)"); Header[] setCookieHeaders = postMethod.getResponseHeaders("Set-Cookie"); for (Header setCookie : setCookieHeaders) { java.util.regex.Matcher matcher = pattern.matcher(setCookie.toString()); if (matcher.find()) { return matcher.group(1); } } return StringUtils.EMPTY; } protected static boolean userAndPasswordAreNotBlank(String user, String pwd) { if (StringUtils.isBlank(user) && StringUtils.isBlank(pwd)) { return false; } return true; } protected Matcher<HttpMethodBase> responsesWith(final int expectedStatusCode) { return new TypeSafeMatcher<HttpMethodBase>() { WeakReference<HttpMethodBase> method; @Override public boolean matchesSafely(HttpMethodBase httpMethodBase) { method = (method == null) ? new WeakReference<>(httpMethodBase) : method; return httpMethodBase.getStatusCode() == expectedStatusCode; } @Override public void describeTo(Description description) { description.appendText("HTTP response ").appendValue(expectedStatusCode) .appendText(" from ").appendText(method.get().getPath()); } @Override protected void describeMismatchSafely(HttpMethodBase item, Description description) { description.appendText("got ").appendValue(item.getStatusCode()).appendText(" ") .appendText(item.getStatusText()); } }; } protected TypeSafeMatcher<String> isJSON() { return new TypeSafeMatcher<String>() { @Override public boolean matchesSafely(String body) { String b = body.trim(); return (b.startsWith("{") && b.endsWith("}")) || (b.startsWith("[") && b.endsWith("]")); } @Override public void describeTo(Description description) { description.appendText("response in JSON format "); } @Override protected void describeMismatchSafely(String item, Description description) { description.appendText("got ").appendText(item); } }; } protected TypeSafeMatcher<String> isValidJSON() { return new TypeSafeMatcher<String>() { @Override public boolean matchesSafely(String body) { boolean isValid = true; try { new JsonParser().parse(body); } catch (JsonParseException e) { LOG.error("Exception in AbstractTestRestApi while matchesSafely ", e); isValid = false; } return isValid; } @Override public void describeTo(Description description) { description.appendText("response in JSON format "); } @Override protected void describeMismatchSafely(String item, Description description) { description.appendText("got ").appendText(item); } }; } protected TypeSafeMatcher<? super JsonElement> hasRootElementNamed(final String memberName) { return new TypeSafeMatcher<JsonElement>() { @Override protected boolean matchesSafely(JsonElement item) { return item.isJsonObject() && item.getAsJsonObject().has(memberName); } @Override public void describeTo(Description description) { description.appendText("response in JSON format with \"").appendText(memberName) .appendText("\" beeing a root element "); } @Override protected void describeMismatchSafely(JsonElement root, Description description) { description.appendText("got ").appendText(root.toString()); } }; } public static void ps() { DefaultExecutor executor = new DefaultExecutor(); executor.setStreamHandler(new PumpStreamHandler(System.out, System.err)); CommandLine cmd = CommandLine.parse("ps"); cmd.addArgument("aux", false); try { executor.execute(cmd); } catch (IOException e) { LOG.error(e.getMessage(), e); } } /** Status code matcher */ protected Matcher<? super HttpMethodBase> isForbidden() { return responsesWith(403); } protected Matcher<? super HttpMethodBase> isAllowed() { return responsesWith(200); } protected Matcher<? super HttpMethodBase> isCreated() { return responsesWith(201); } protected Matcher<? super HttpMethodBase> isBadRequest() { return responsesWith(400); } protected Matcher<? super HttpMethodBase> isNotFound() { return responsesWith(404); } protected Matcher<? super HttpMethodBase> isNotAllowed() { return responsesWith(405); } }