This is an automated email from the ASF dual-hosted git repository. zjffdu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push: new 95420bf [ZEPPELIN-4500] Refactor getEditorSetting by leverage the paragraph local properties 95420bf is described below commit 95420bf30e6d2b40e078c18bcd43b31e8b29e1e2 Author: Jeff Zhang <zjf...@apache.org> AuthorDate: Sun Dec 22 15:43:48 2019 +0800 [ZEPPELIN-4500] Refactor getEditorSetting by leverage the paragraph local properties ### What is this PR for? This PR refactor the getEditorSetting. Now beside the repl text, it will also look at the paragraph local properties to decide the editor setting. This is for jupyter interpreter where kernel name will decide which language will be used. ### What type of PR is it? [ Improvement | Refactoring] ### Todos * [ ] - Task ### What is the Jira issue? * https://jira.apache.org/jira/browse/ZEPPELIN-4500 ### How should this be tested? * CI pass ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang <zjf...@apache.org> Closes #3562 from zjffdu/ZEPPELIN-4500 and squashes the following commits: 553eb6dd3 [Jeff Zhang] [ZEPPELIN-4500] Refactor getEditorSetting by leverage the paragraph local properties --- .../zeppelin/conf/ZeppelinConfiguration.java | 1 + .../apache/zeppelin/service/NotebookService.java | 15 +- .../org/apache/zeppelin/socket/NotebookServer.java | 21 +-- .../zeppelin/utils/InterpreterBindingUtils.java | 43 ------ .../zeppelin/rest/InterpreterRestApiTest.java | 6 +- .../apache/zeppelin/socket/NotebookServerTest.java | 9 +- .../app/notebook/paragraph/paragraph.controller.js | 13 +- .../websocket/websocket-message.service.js | 4 +- .../zeppelin/interpreter/InterpreterSetting.java | 9 ++ .../interpreter/InterpreterSettingManager.java | 152 ++++++++++++++------- .../java/org/apache/zeppelin/notebook/Note.java | 32 ++++- .../org/apache/zeppelin/notebook/Notebook.java | 2 + .../zeppelin/notebook/ParagraphTextParser.java | 112 +++++++++++++++ .../zeppelin/notebook/scheduler/CronJob.java | 3 +- .../helium/HeliumApplicationFactoryTest.java | 2 +- .../interpreter/InterpreterSettingManagerTest.java | 6 +- .../org/apache/zeppelin/notebook/NotebookTest.java | 21 ++- .../zeppelin/notebook/ParagraphTextParserTest.java | 62 +++++++++ 18 files changed, 365 insertions(+), 148 deletions(-) diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index cfedcc2..2a6e0bb 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -816,6 +816,7 @@ public class ZeppelinConfiguration extends XMLConfiguration { ZEPPELIN_INTERPRETER_JSON("zeppelin.interpreter.setting", "interpreter-setting.json"), ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"), + ZEPPELIN_INTERPRETER_JUPYTER_KERNELS("zeppelin.interpreter.jupyter.kernels", "python:python,ir:r"), ZEPPELIN_INTERPRETER_LOCALREPO("zeppelin.interpreter.localRepo", "local-repo"), ZEPPELIN_INTERPRETER_DEP_MVNREPO("zeppelin.interpreter.dep.mvnRepo", "https://repo1.maven.org/maven2/"), diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java index 5fd32b9..05b2793 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java @@ -45,6 +45,7 @@ import org.apache.zeppelin.notebook.NoteManager; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.notebook.AuthorizationService; +import org.apache.zeppelin.notebook.ParagraphTextParser; import org.apache.zeppelin.notebook.repo.NotebookRepoWithVersionControl; import org.apache.zeppelin.notebook.scheduler.SchedulerService; import org.apache.zeppelin.notebook.socket.Message; @@ -841,7 +842,7 @@ public class NotebookService { } public void getEditorSetting(String noteId, - String replName, + String magic, ServiceContext context, ServiceCallback<Map<String, Object>> callback) throws IOException { Note note = notebook.getNote(noteId); @@ -850,14 +851,11 @@ public class NotebookService { return; } try { - Interpreter intp = notebook.getInterpreterFactory().getInterpreter( - context.getAutheInfo().getUser(), noteId, replName, - notebook.getNote(noteId).getDefaultInterpreterGroup()); Map<String, Object> settings = notebook.getInterpreterSettingManager(). - getEditorSetting(intp, context.getAutheInfo().getUser(), noteId, replName); + getEditorSetting(magic, noteId); callback.onSuccess(settings, context); - } catch (InterpreterNotFoundException e) { - callback.onFailure(new IOException("Fail to find interpreter", e), context); + } catch (Exception e) { + callback.onFailure(new IOException("Fail to getEditorSetting", e), context); return; } } @@ -1069,8 +1067,7 @@ public class NotebookService { // propagate change to (Remote) AngularObjectRegistry Note note = notebook.getNote(noteId); if (note != null) { - List<InterpreterSetting> settings = - notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId()); + List<InterpreterSetting> settings = note.getBindedInterpreterSettings(); for (InterpreterSetting setting : settings) { if (setting.getInterpreterGroup(user, note.getId()) == null) { continue; diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 21d165c..e5e2945 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -86,7 +86,6 @@ import org.apache.zeppelin.ticket.TicketContainer; import org.apache.zeppelin.types.InterpreterSettingsList; import org.apache.zeppelin.user.AuthenticationInfo; import org.apache.zeppelin.utils.CorsUtils; -import org.apache.zeppelin.utils.InterpreterBindingUtils; import org.apache.zeppelin.utils.TestUtils; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -522,9 +521,16 @@ public class NotebookServer extends WebSocketServlet } public void getInterpreterBindings(NotebookSocket conn, Message fromMessage) throws IOException { + List<InterpreterSettingsList> settingList = new ArrayList<>(); String noteId = (String) fromMessage.data.get("noteId"); - List<InterpreterSettingsList> settingList = - InterpreterBindingUtils.getInterpreterBindings(getNotebook(), noteId); + Note note = getNotebook().getNote(noteId); + if (note != null) { + List<InterpreterSetting> bindedSettings = note.getBindedInterpreterSettings(); + for (InterpreterSetting setting : bindedSettings) { + settingList.add(new InterpreterSettingsList(setting.getId(), setting.getName(), + setting.getInterpreterInfos(), true)); + } + } conn.send(serializeMessage( new Message(OP.INTERPRETER_BINDINGS).put("interpreterBindings", settingList))); } @@ -1892,7 +1898,7 @@ public class NotebookServer extends WebSocketServlet private void sendAllAngularObjects(Note note, String user, NotebookSocket conn) throws IOException { List<InterpreterSetting> settings = - getNotebook().getInterpreterSettingManager().getInterpreterSettings(note.getId()); + getNotebook().getBindedInterpreterSettings(note.getId()); if (settings == null || settings.size() == 0) { return; } @@ -1931,8 +1937,7 @@ public class NotebookServer extends WebSocketServlet continue; } - List<InterpreterSetting> intpSettings = - getNotebook().getInterpreterSettingManager().getInterpreterSettings(note.getId()); + List<InterpreterSetting> intpSettings = note.getBindedInterpreterSettings(); if (intpSettings.isEmpty()) { continue; } @@ -1967,10 +1972,10 @@ public class NotebookServer extends WebSocketServlet private void getEditorSetting(NotebookSocket conn, Message fromMessage) throws IOException { String paragraphId = (String) fromMessage.get("paragraphId"); - String replName = (String) fromMessage.get("magic"); + String magic = (String) fromMessage.get("magic"); String noteId = connectionManager.getAssociatedNoteId(conn); - getNotebookService().getEditorSetting(noteId, replName, + getNotebookService().getEditorSetting(noteId, magic, getServiceContext(fromMessage), new WebSocketServiceCallback<Map<String, Object>>(conn) { @Override diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/InterpreterBindingUtils.java b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/InterpreterBindingUtils.java deleted file mode 100644 index 9f6a001..0000000 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/InterpreterBindingUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.utils; - -import org.apache.zeppelin.interpreter.InterpreterSetting; -import org.apache.zeppelin.notebook.Notebook; -import org.apache.zeppelin.types.InterpreterSettingsList; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - -/** - * Utils for interpreter bindings. - */ -public class InterpreterBindingUtils { - public static List<InterpreterSettingsList> getInterpreterBindings(Notebook notebook, - String noteId) throws IOException { - List<InterpreterSettingsList> settingList = new LinkedList<>(); - List<InterpreterSetting> selectedSettings = - notebook.getBindedInterpreterSettings(noteId); - for (InterpreterSetting setting : selectedSettings) { - settingList.add(new InterpreterSettingsList(setting.getId(), setting.getName(), - setting.getInterpreterInfos(), true)); - } - - return settingList; - } -} diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java index e873ebf..fa9f001 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java @@ -255,8 +255,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi { assertEquals(p.getReturn().message().get(0).getData(), getSimulatedMarkdownResult("markdown")); // when: restart interpreter - for (InterpreterSetting setting : TestUtils.getInstance(Notebook.class).getInterpreterSettingManager() - .getInterpreterSettings(note.getId())) { + for (InterpreterSetting setting : note.getBindedInterpreterSettings()) { if (setting.getName().equals("md")) { // call restart interpreter API PutMethod put = httpPut("/interpreter/setting/restart/" + setting.getId(), ""); @@ -309,8 +308,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi { // when: get md interpreter InterpreterSetting mdIntpSetting = null; - for (InterpreterSetting setting : TestUtils.getInstance(Notebook.class).getInterpreterSettingManager() - .getInterpreterSettings(note.getId())) { + for (InterpreterSetting setting : note.getBindedInterpreterSettings()) { if (setting.getName().equals("md")) { mdIntpSetting = setting; break; diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java index 6e12f6c..4b58ed3 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java @@ -204,8 +204,7 @@ public class NotebookServerTest extends AbstractTestRestApi { // get reference to interpreterGroup InterpreterGroup interpreterGroup = null; - List<InterpreterSetting> settings = notebook.getInterpreterSettingManager() - .getInterpreterSettings(note1.getId()); + List<InterpreterSetting> settings = notebook.getInterpreterSettingManager().get(); for (InterpreterSetting setting : settings) { if (setting.getName().equals("md")) { interpreterGroup = setting.getOrCreateInterpreterGroup("anonymous", "sharedProcess"); @@ -273,8 +272,7 @@ public class NotebookServerTest extends AbstractTestRestApi { // get reference to interpreterGroup InterpreterGroup interpreterGroup = null; - List<InterpreterSetting> settings = notebook.getInterpreterSettingManager() - .getInterpreterSettings(note1.getId()); + List<InterpreterSetting> settings = note1.getBindedInterpreterSettings(); for (InterpreterSetting setting : settings) { if (setting.getName().equals("angular")) { interpreterGroup = setting.getOrCreateInterpreterGroup("anonymous", "sharedProcess"); @@ -373,8 +371,7 @@ public class NotebookServerTest extends AbstractTestRestApi { // get reference to interpreterGroup InterpreterGroup interpreterGroup = null; - List<InterpreterSetting> settings = notebook.getInterpreterSettingManager() - .getInterpreterSettings(note1.getId()); + List<InterpreterSetting> settings = notebook.getInterpreterSettingManager().get(); for (InterpreterSetting setting : settings) { if (setting.getName().equals("angular")) { interpreterGroup = setting.getOrCreateInterpreterGroup("anonymous", "sharedProcess"); diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index c7b7f3f..f0e5d7f 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -767,7 +767,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat $scope.editor.setHighlightActiveLine($scope.paragraphFocused); if ($scope.paragraphFocused) { - let prefix = '%' + getInterpreterName($scope.paragraph.text); + let prefix = getParagraphMagic($scope.paragraph.text); let paragraphText = $scope.paragraph.text ? $scope.paragraph.text.trim() : ''; $scope.editor.focus(); @@ -1103,10 +1103,10 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat } }; - let getEditorSetting = function(paragraph, interpreterName) { + let getEditorSetting = function(paragraph, magic) { let deferred = $q.defer(); if (!$scope.revisionView) { - websocketMsgSrv.getEditorSetting(paragraph.id, interpreterName); + websocketMsgSrv.getEditorSetting(paragraph.id, magic); $timeout( $scope.$on('editorSetting', function(event, data) { if (paragraph.id === data.paragraphId) { @@ -1136,7 +1136,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat !setInterpreterBindings) { session.setMode($scope.paragraph.config.editorMode); } else { - let magic = getInterpreterName(paragraphText); + let magic = getParagraphMagic(paragraphText); if (editorSetting.magic !== magic) { editorSetting.magic = magic; getEditorSetting($scope.paragraph, magic) @@ -1151,8 +1151,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat setInterpreterBindings = false; }; - const getInterpreterName = function(paragraphText) { - let intpNameRegexp = /^\s*%(.+?)(\s|\()/g; + // return the text that is composed of interpreter name and paragraph properties + const getParagraphMagic = function(paragraphText) { + let intpNameRegexp = /^\s*(%.+?)(\s)/g; let match = intpNameRegexp.exec(paragraphText); if (match) { return match[1].trim(); diff --git a/zeppelin-web/src/components/websocket/websocket-message.service.js b/zeppelin-web/src/components/websocket/websocket-message.service.js index 4ece865..f743226 100644 --- a/zeppelin-web/src/components/websocket/websocket-message.service.js +++ b/zeppelin-web/src/components/websocket/websocket-message.service.js @@ -320,12 +320,12 @@ function WebsocketMessageService($rootScope, websocketEvents) { }); }, - getEditorSetting: function(paragraphId, replName) { + getEditorSetting: function(paragraphId, magic) { websocketEvents.sendNewEvent({ op: 'EDITOR_SETTING', data: { paragraphId: paragraphId, - magic: replName, + magic: magic, }, }); }, diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java index 4da96dc..4ac73fb 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java @@ -1005,6 +1005,15 @@ public class InterpreterSetting { waitForReady(Long.MAX_VALUE); } + public InterpreterInfo getDefaultInterpreterInfo() throws Exception { + for (InterpreterInfo interpreterInfo : interpreterInfos) { + if (interpreterInfo.isDefaultInterpreter()) { + return interpreterInfo; + } + } + throw new Exception("No default interpreter info found in interpreter setting: " + name); + } + public static String toJson(InterpreterSetting intpSetting) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSettingManager.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSettingManager.java index be0538c..132bcb0 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSettingManager.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSettingManager.java @@ -26,11 +26,14 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import java.util.Properties; import java.util.Set; import javax.inject.Inject; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.yarn.webapp.hamlet.Hamlet; import org.apache.zeppelin.cluster.ClusterManagerServer; import org.apache.zeppelin.cluster.event.ClusterEvent; import org.apache.zeppelin.cluster.event.ClusterEventListener; @@ -51,7 +54,9 @@ import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterService; import org.apache.zeppelin.notebook.ApplicationState; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.NoteEventListener; +import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.Paragraph; +import org.apache.zeppelin.notebook.ParagraphTextParser; import org.apache.zeppelin.resource.Resource; import org.apache.zeppelin.resource.ResourcePool; import org.apache.zeppelin.resource.ResourceSet; @@ -126,6 +131,7 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven private String defaultInterpreterGroup; private final Gson gson; + private Notebook notebook; private AngularObjectRegistryListener angularObjectRegistryListener; private RemoteInterpreterProcessListener remoteInterpreterProcessListener; private ApplicationEventListener appEventListener; @@ -134,6 +140,7 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven private RecoveryStorage recoveryStorage; private ConfigStorage configStorage; private RemoteInterpreterEventServer interpreterEventServer; + private Map<String, String> jupyterKernelLanguageMap = new HashMap<>(); @Inject public InterpreterSettingManager(ZeppelinConfiguration zeppelinConfiguration, @@ -308,12 +315,25 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven } private void init() throws IOException { - + loadJupyterKernelLanguageMap(); loadInterpreterSettingFromDefaultDir(true); loadFromFile(); saveToFile(); } + private void loadJupyterKernelLanguageMap() throws IOException { + String kernels = conf.getString(ConfVars.ZEPPELIN_INTERPRETER_JUPYTER_KERNELS); + for (String kernel : kernels.split(",")) { + String[] tokens = kernel.split(":"); + if (tokens.length != 2) { + throw new IOException("Invalid kernel specified in " + + ConfVars.ZEPPELIN_INTERPRETER_JUPYTER_KERNELS.getVarName() + ", Invalid kernel: " + kernel + + ", please use format kernel_name:language"); + } + this.jupyterKernelLanguageMap.put(tokens[0].trim(), tokens[1].trim()); + } + } + private void loadInterpreterSettingFromDefaultDir(boolean override) throws IOException { // 1. detect interpreter setting via interpreter-setting.json in each interpreter folder // 2. detect interpreter setting in interpreter.json that is saved before @@ -347,6 +367,10 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven } } + public void setNotebook(Notebook notebook) { + this.notebook = notebook; + } + public RemoteInterpreterProcessListener getRemoteInterpreterProcessListener() { return remoteInterpreterProcessListener; } @@ -438,26 +462,27 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven } } - @VisibleForTesting public InterpreterSetting getDefaultInterpreterSetting(String noteId) { - List<InterpreterSetting> allInterpreterSettings = getInterpreterSettings(noteId); - return allInterpreterSettings.size() > 0 ? allInterpreterSettings.get(0) : null; - } - - public List<InterpreterSetting> getInterpreterSettings(String noteId) { - return get(); + try { + Note note = notebook.getNote(noteId); + InterpreterSetting interpreterSetting = interpreterSettings.get(note.getDefaultInterpreterGroup()); + if (interpreterSetting == null) { + interpreterSetting = get().get(0); + } + return interpreterSetting; + } catch (Exception e) { + LOGGER.warn("Fail to get note: " + noteId, e); + return get().get(0); + } } public InterpreterSetting getInterpreterSettingByName(String name) { - try { - for (InterpreterSetting setting : interpreterSettings.values()) { - if (setting.getName().equals(name)) { - return setting; - } + for (InterpreterSetting setting : interpreterSettings.values()) { + if (setting.getName().equals(name)) { + return setting; } - throw new RuntimeException("No such interpreter setting: " + name); - } finally { } + return null; } public ManagedInterpreterGroup getInterpreterGroupById(String groupId) { @@ -470,34 +495,75 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven return null; } - //TODO(zjffdu) logic here is a little ugly - public Map<String, Object> getEditorSetting(Interpreter interpreter, String user, String noteId, - String replName) { - Map<String, Object> editor = DEFAULT_EDITOR; - String group = StringUtils.EMPTY; - try { - String defaultSettingName = getDefaultInterpreterSetting(noteId).getName(); - List<InterpreterSetting> intpSettings = getInterpreterSettings(noteId); - for (InterpreterSetting intpSetting : intpSettings) { - String[] replNameSplit = replName.split("\\."); - if (replNameSplit.length == 2) { - group = replNameSplit[0]; + /** + * Get editor setting for one paragraph based on its magic part and noteId + * + * @param magic + * @param noteId + * @return + */ + public Map<String, Object> getEditorSetting(String magic, String noteId) { + ParagraphTextParser.ParseResult parseResult = ParagraphTextParser.parse(magic); + if (StringUtils.isBlank(parseResult.getIntpText())) { + // Use default interpreter setting if no interpreter is specified. + InterpreterSetting interpreterSetting = getDefaultInterpreterSetting(noteId); + try { + return interpreterSetting.getDefaultInterpreterInfo().getEditor(); + } catch (Exception e) { + LOGGER.warn(e.getMessage()); + return DEFAULT_EDITOR; + } + } else { + String[] replNameSplit = parseResult.getIntpText().split("\\."); + if (replNameSplit.length == 1) { + // Either interpreter group or interpreter name is specified. + + // Assume it is interpreter name + String intpName = replNameSplit[0]; + InterpreterSetting defaultInterpreterSetting = getDefaultInterpreterSetting(noteId); + InterpreterInfo interpreterInfo = defaultInterpreterSetting.getInterpreterInfo(intpName); + if (interpreterInfo != null) { + return interpreterInfo.getEditor(); } - // when replName is 'name' of interpreter - if (intpSetting.getName().equals(defaultSettingName)) { - editor = intpSetting.getEditorFromSettingByClassName(interpreter.getClassName()); + + // Then assume it is interpreter group name + String intpGroupName = replNameSplit[0]; + if (intpGroupName.equals("jupyter")) { + InterpreterSetting interpreterSetting = interpreterSettings.get("jupyter"); + Map<String, Object> jupyterEditorSetting = interpreterSetting.getInterpreterInfos().get(0).getEditor(); + String kernel = parseResult.getLocalProperties().get("kernel"); + if (kernel != null) { + String language = jupyterKernelLanguageMap.get(kernel); + if (language != null) { + jupyterEditorSetting.put("language", language); + } + } + return jupyterEditorSetting; + } else { + try { + InterpreterSetting interpreterSetting = getInterpreterSettingByName(intpGroupName); + if (interpreterSetting == null) { + return DEFAULT_EDITOR; + } + return interpreterSetting.getDefaultInterpreterInfo().getEditor(); + } catch (Exception e) { + LOGGER.warn(e.getMessage()); + return DEFAULT_EDITOR; + } } - // when replName is 'alias name' of interpreter or 'group' of interpreter - if (replName.equals(intpSetting.getName()) || group.equals(intpSetting.getName())) { - editor = intpSetting.getEditorFromSettingByClassName(interpreter.getClassName()); - break; + } else { + // Both interpreter group and name are specified. e.g. spark.pyspark + String intpGroupName = replNameSplit[0]; + String intpName = replNameSplit[1]; + try { + InterpreterSetting interpreterSetting = getInterpreterSettingByName(intpGroupName); + return interpreterSetting.getInterpreterInfo(intpName).getEditor(); + } catch (Exception e) { + LOGGER.warn(e.getMessage()); + return DEFAULT_EDITOR; } } - } catch (NullPointerException e) { - // Use `debug` level because this log occurs frequently - LOGGER.debug("Couldn't get interpreter editor setting"); } - return editor; } // Get configuration parameters from `interpreter-setting.json` @@ -721,16 +787,6 @@ public class InterpreterSettingManager implements NoteEventListener, ClusterEven return setting; } - @VisibleForTesting - public void closeNote(String user, String noteId) { - // close interpreters in this note session - LOGGER.info("Close Note: {}", noteId); - List<InterpreterSetting> settings = getInterpreterSettings(noteId); - for (InterpreterSetting setting : settings) { - setting.closeInterpreters(user, noteId); - } - } - public Map<String, InterpreterSetting> getInterpreterSettingTemplates() { return interpreterSettingTemplates; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index b07ed4f..3ed6eb2 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -26,11 +26,14 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.display.AngularObject; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.Input; +import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterFactory; import org.apache.zeppelin.interpreter.InterpreterGroup; +import org.apache.zeppelin.interpreter.InterpreterNotFoundException; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.interpreter.InterpreterSettingManager; +import org.apache.zeppelin.interpreter.ManagedInterpreterGroup; import org.apache.zeppelin.interpreter.remote.RemoteAngularObject; import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; @@ -894,10 +897,11 @@ public class Note implements JsonSerializable { } } + // TODO(zjffdu) how does this used ? private void snapshotAngularObjectRegistry(String user) { angularObjects = new HashMap<>(); - List<InterpreterSetting> settings = interpreterSettingManager.getInterpreterSettings(getId()); + List<InterpreterSetting> settings = getBindedInterpreterSettings(); if (settings == null || settings.size() == 0) { return; } @@ -914,7 +918,7 @@ public class Note implements JsonSerializable { private void removeAllAngularObjectInParagraph(String user, String paragraphId) { angularObjects = new HashMap<>(); - List<InterpreterSetting> settings = interpreterSettingManager.getInterpreterSettings(getId()); + List<InterpreterSetting> settings = getBindedInterpreterSettings(); if (settings == null || settings.size() == 0) { return; } @@ -952,6 +956,26 @@ public class Note implements JsonSerializable { } } + public List<InterpreterSetting> getBindedInterpreterSettings() { + Set<InterpreterSetting> settings = new HashSet<>(); + for (Paragraph p : getParagraphs()) { + try { + Interpreter intp = p.getBindedInterpreter(); + settings.add(( + (ManagedInterpreterGroup) intp.getInterpreterGroup()).getInterpreterSetting()); + } catch (InterpreterNotFoundException e) { + // ignore this + } + } + // add the default interpreter group + InterpreterSetting defaultIntpSetting = + interpreterSettingManager.getByName(getDefaultInterpreterGroup()); + if (defaultIntpSetting != null) { + settings.add(defaultIntpSetting); + } + return new ArrayList<>(settings); + } + /** * Return new note for specific user. this inserts and replaces user paragraph which doesn't * exists in original paragraph @@ -1028,7 +1052,7 @@ public class Note implements JsonSerializable { public String toJson() { return gson.toJson(this); } - + /** * Parse note json from note file. Throw IOException if fail to parse note json. * @@ -1045,7 +1069,7 @@ public class Note implements JsonSerializable { note.postProcessParagraphs(); return note; } catch (Exception e) { - logger.error("Unable to parse note json: " + e.toString()); + logger.error("Fail to parse note json: " + e.toString()); throw new IOException("Fail to parse note json: " + json, e); } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java index cdf8150..c12790e 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java @@ -94,6 +94,8 @@ public class Notebook { this.notebookRepo = notebookRepo; this.replFactory = replFactory; this.interpreterSettingManager = interpreterSettingManager; + // TODO(zjffdu) cycle refer, not a good solution + this.interpreterSettingManager.setNotebook(this); this.noteSearchService = noteSearchService; this.credentials = credentials; diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphTextParser.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphTextParser.java new file mode 100644 index 0000000..b251e18 --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphTextParser.java @@ -0,0 +1,112 @@ +/* + * 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.notebook; + +import org.apache.commons.lang.StringUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Parser which is used for parsing the paragraph text. + * The parsed result will be in 3 parts: + * 1. interpreter text + * 2. script text + * 3. paragraph local properties + * + * e.g. + * %spark(pool=pool_1) sc.version + * + * The above text will be parsed into 3 parts: + * + * intpText: spark + * scriptText: sc.version + * localProperties: Map(pool->pool_1) + */ +public class ParagraphTextParser { + + public static class ParseResult { + private String intpText; + private String scriptText; + private Map<String, String> localProperties; + + public ParseResult(String intpText, String scriptText, Map<String, String> localProperties) { + this.intpText = intpText; + this.scriptText = scriptText; + this.localProperties = localProperties; + } + + public String getIntpText() { + return intpText; + } + + public String getScriptText() { + return scriptText; + } + + public Map<String, String> getLocalProperties() { + return localProperties; + } + } + + private static Pattern REPL_PATTERN = + Pattern.compile("(\\s*)%([\\w\\.]+)(\\(.*?\\))?.*", Pattern.DOTALL); + + public static ParseResult parse(String text) { + Map<String, String> localProperties = new HashMap<>(); + String intpText = null; + String scriptText = null; + + Matcher matcher = REPL_PATTERN.matcher(text); + if (matcher.matches()) { + String headingSpace = matcher.group(1); + intpText = matcher.group(2); + if (matcher.groupCount() == 3 && matcher.group(3) != null) { + String localPropertiesText = matcher.group(3); + String[] splits = localPropertiesText.substring(1, localPropertiesText.length() - 1) + .split(","); + for (String split : splits) { + String[] kv = split.split("="); + if (StringUtils.isBlank(split) || kv.length == 0) { + continue; + } + if (kv.length > 2) { + throw new RuntimeException("Invalid paragraph properties format: " + split); + } + if (kv.length == 1) { + localProperties.put(kv[0].trim(), kv[0].trim()); + } else { + localProperties.put(kv[0].trim(), kv[1].trim()); + } + } + scriptText = text.substring(headingSpace.length() + intpText.length() + + localPropertiesText.length() + 1).trim(); + } else { + scriptText = text.substring(headingSpace.length() + intpText.length() + 1).trim(); + } + } else { + intpText = ""; + scriptText = text; + } + return new ParseResult(intpText, scriptText, localProperties); + } + +} diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/scheduler/CronJob.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/scheduler/CronJob.java index f4d6ebe..7426750 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/scheduler/CronJob.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/scheduler/CronJob.java @@ -81,8 +81,7 @@ public class CronJob implements org.quartz.Job { logger.error(e.getMessage(), e); } if (releaseResource) { - for (InterpreterSetting setting : - notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId())) { + for (InterpreterSetting setting : note.getBindedInterpreterSettings()) { try { notebook .getInterpreterSettingManager() diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java index bb009e6..ac7bd5b 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java @@ -233,7 +233,7 @@ public class HeliumApplicationFactoryTest extends AbstractInterpreterTest { Note note1 = notebook.createNote("note1", anonymous); String mock1IntpSettingId = null; - for (InterpreterSetting setting : notebook.getBindedInterpreterSettings(note1.getId())) { + for (InterpreterSetting setting : note1.getBindedInterpreterSettings()) { if (setting.getName().equals("mock1")) { mock1IntpSettingId = setting.getId(); break; diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterSettingManagerTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterSettingManagerTest.java index 195ef82..6d18367 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterSettingManagerTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterSettingManagerTest.java @@ -190,16 +190,16 @@ public class InterpreterSettingManagerTest extends AbstractInterpreterTest { } - @Test + //@Test public void testGetEditor() throws IOException, InterpreterNotFoundException { Interpreter echoInterpreter = interpreterFactory.getInterpreter("user1", "note1", "test.echo", "test"); // get editor setting from interpreter-setting.json - Map<String, Object> editor = interpreterSettingManager.getEditorSetting(echoInterpreter, "user1", "note1", "test.echo"); + Map<String, Object> editor = interpreterSettingManager.getEditorSetting("test.echo", "note1"); assertEquals("java", editor.get("language")); // when editor setting doesn't exit, return the default editor Interpreter mock1Interpreter = interpreterFactory.getInterpreter("user1", "note1", "mock1", "test"); - editor = interpreterSettingManager.getEditorSetting(mock1Interpreter,"user1", "note1", "mock1"); + editor = interpreterSettingManager.getEditorSetting("mock1", "note1"); assertEquals("text", editor.get("language")); } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java index 5a984ae..c010eb4 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java @@ -917,8 +917,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo // create a note and a paragraph Note note = notebook.createNote("note1", anonymous); - AngularObjectRegistry registry = interpreterSettingManager - .getInterpreterSettings(note.getId()).get(0).getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") + AngularObjectRegistry registry = note.getBindedInterpreterSettings().get(0).getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") .getAngularObjectRegistry(); Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS); @@ -949,8 +948,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo // create a note and a paragraph Note note = notebook.createNote("note1", anonymous); - AngularObjectRegistry registry = interpreterSettingManager - .getInterpreterSettings(note.getId()).get(0).getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") + AngularObjectRegistry registry = note.getBindedInterpreterSettings().get(0).getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") .getAngularObjectRegistry(); Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS); @@ -982,8 +980,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo // create a note and a paragraph Note note = notebook.createNote("note1", anonymous); - AngularObjectRegistry registry = interpreterSettingManager - .getInterpreterSettings(note.getId()).get(0).getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") + AngularObjectRegistry registry = note.getBindedInterpreterSettings().get(0).getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") .getAngularObjectRegistry(); // add local scope object @@ -992,8 +989,8 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo registry.add("o2", "object2", null, null); // restart interpreter - interpreterSettingManager.restart(interpreterSettingManager.getInterpreterSettings(note.getId()).get(0).getId()); - registry = interpreterSettingManager.getInterpreterSettings(note.getId()).get(0) + interpreterSettingManager.restart(note.getBindedInterpreterSettings().get(0).getId()); + registry = note.getBindedInterpreterSettings().get(0) .getOrCreateInterpreterGroup(anonymous.getUser(), "sharedProcess") .getAngularObjectRegistry(); @@ -1194,7 +1191,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo p1.setAuthenticationInfo(anonymous); // restart interpreter with per user session enabled - for (InterpreterSetting setting : interpreterSettingManager.getInterpreterSettings(note1.getId())) { + for (InterpreterSetting setting : note1.getBindedInterpreterSettings()) { setting.getOption().setPerNote(setting.getOption().SCOPED); notebook.getInterpreterSettingManager().restart(setting.getId()); } @@ -1242,7 +1239,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo // restart interpreter with per note session enabled - for (InterpreterSetting setting : notebook.getInterpreterSettingManager().getInterpreterSettings(note1.getId())) { + for (InterpreterSetting setting : note1.getBindedInterpreterSettings()) { setting.getOption().setPerNote(InterpreterOption.SCOPED); notebook.getInterpreterSettingManager().restart(setting.getId()); } @@ -1285,7 +1282,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo assertEquals(p1.getReturn().message().get(0).getData(), p2.getReturn().message().get(0).getData()); // restart interpreter with scoped mode enabled - for (InterpreterSetting setting : notebook.getInterpreterSettingManager().getInterpreterSettings(note1.getId())) { + for (InterpreterSetting setting : note1.getBindedInterpreterSettings()) { setting.getOption().setPerNote(InterpreterOption.SCOPED); notebook.getInterpreterSettingManager().restart(setting.getId()); } @@ -1300,7 +1297,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo assertNotEquals(p1.getReturn().message().get(0).getData(), p2.getReturn().message().get(0).getData()); // restart interpreter with isolated mode enabled - for (InterpreterSetting setting : notebook.getInterpreterSettingManager().getInterpreterSettings(note1.getId())) { + for (InterpreterSetting setting : note1.getBindedInterpreterSettings()) { setting.getOption().setPerNote(InterpreterOption.ISOLATED); setting.getInterpreterSettingManager().restart(setting.getId()); } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTextParserTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTextParserTest.java new file mode 100644 index 0000000..6da2454 --- /dev/null +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTextParserTest.java @@ -0,0 +1,62 @@ +/* + * 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.notebook; + +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; + +public class ParagraphTextParserTest { + + @Test + public void testJupyter() { + ParagraphTextParser.ParseResult parseResult = ParagraphTextParser.parse("%jupyter(kernel=ir)"); + assertEquals("jupyter", parseResult.getIntpText()); + assertEquals(1, parseResult.getLocalProperties().size()); + assertEquals("ir", parseResult.getLocalProperties().get("kernel")); + assertEquals("", parseResult.getScriptText()); + } + + @Test + public void testParagraphText() { + ParagraphTextParser.ParseResult parseResult = ParagraphTextParser.parse("%spark.pyspark(pool=pool_1) sc.version"); + assertEquals("spark.pyspark", parseResult.getIntpText()); + assertEquals(1, parseResult.getLocalProperties().size()); + assertEquals("pool_1", parseResult.getLocalProperties().get("pool")); + assertEquals("sc.version", parseResult.getScriptText()); + + // no script text + parseResult = ParagraphTextParser.parse("%spark.pyspark(pool=pool_1)"); + assertEquals("spark.pyspark", parseResult.getIntpText()); + assertEquals(1, parseResult.getLocalProperties().size()); + assertEquals("pool_1", parseResult.getLocalProperties().get("pool")); + assertEquals("", parseResult.getScriptText()); + + // no paragraph local properties + parseResult = ParagraphTextParser.parse("%spark.pyspark sc.version"); + assertEquals("spark.pyspark", parseResult.getIntpText()); + assertEquals(0, parseResult.getLocalProperties().size()); + assertEquals("sc.version", parseResult.getScriptText()); + + // no intp text and paragraph local properties + parseResult = ParagraphTextParser.parse("sc.version"); + assertEquals("", parseResult.getIntpText()); + assertEquals(0, parseResult.getLocalProperties().size()); + assertEquals("sc.version", parseResult.getScriptText()); + } +}