Skip to content

Commit 1806fe2

Browse files
committed
[ZEPPELIN-5745] Add headless CLI to run a note without starting the server
Add a bin/run-note.sh CLI that executes a single note headlessly -- no Jetty/REST/WebSocket -- by assembling the interpreter runtime directly, in the spirit of papermill for Jupyter. Supports parameter injection via -p (${var} placeholders) and saving results to a separate note via -o. A failed paragraph exits non-zero so CI/batch callers detect the failure. On exit the process tears down its interpreter processes, RemoteScheduler pools, the event server, and ExecutorFactory pools, then System.exit()s so the JVM does not hang on the runtime's non-daemon threads.
1 parent 6dfdada commit 1806fe2

14 files changed

Lines changed: 1729 additions & 0 deletions

bin/run-note.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
#
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
#
19+
# Run a Zeppelin note headlessly, without starting Zeppelin Server
20+
#
21+
22+
bin=$(dirname "${BASH_SOURCE-$0}")
23+
bin=$(cd "${bin}">/dev/null; pwd)
24+
25+
. "${bin}/common.sh"
26+
27+
ZEPPELIN_RUN_NOTE_MAIN=org.apache.zeppelin.notebook.cli.NotebookRunner
28+
ZEPPELIN_LOGFILE="${ZEPPELIN_LOG_DIR}/run-note.log"
29+
JAVA_OPTS+=" -Dzeppelin.log.file=${ZEPPELIN_LOGFILE}"
30+
31+
if [[ -d "${ZEPPELIN_HOME}/zeppelin-server/target/classes" ]]; then
32+
ZEPPELIN_CLASSPATH+=":${ZEPPELIN_HOME}/zeppelin-server/target/classes"
33+
fi
34+
35+
if [[ -d "${ZEPPELIN_HOME}/zeppelin-interpreter/target/classes" ]]; then
36+
ZEPPELIN_CLASSPATH+=":${ZEPPELIN_HOME}/zeppelin-interpreter/target/classes"
37+
fi
38+
39+
addJarInDir "${ZEPPELIN_HOME}/zeppelin-interpreter/target/lib"
40+
addJarInDir "${ZEPPELIN_HOME}/zeppelin-server/target/lib"
41+
addJarInDir "${ZEPPELIN_HOME}/lib"
42+
addJarInDir "${ZEPPELIN_HOME}/lib/interpreter"
43+
44+
CLASSPATH+=":${ZEPPELIN_CLASSPATH}"
45+
$ZEPPELIN_RUNNER $JAVA_OPTS -cp $CLASSPATH $ZEPPELIN_RUN_NOTE_MAIN ${@}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.zeppelin.notebook.cli;
18+
19+
import org.apache.zeppelin.display.AngularObject;
20+
import org.apache.zeppelin.display.AngularObjectRegistryListener;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
24+
/**
25+
* Headless implementation of {@link AngularObjectRegistryListener}. There is no UI to broadcast
26+
* angular object changes to in a headless run, so this only logs at debug level.
27+
*/
28+
public class HeadlessAngularObjectListener implements AngularObjectRegistryListener {
29+
30+
private static final Logger LOGGER = LoggerFactory.getLogger(HeadlessAngularObjectListener.class);
31+
32+
@Override
33+
public void onAddAngularObject(String interpreterGroupId, AngularObject angularObject) {
34+
LOGGER.debug("Angular object added in group {}: {}", interpreterGroupId,
35+
angularObject.getName());
36+
}
37+
38+
@Override
39+
public void onUpdateAngularObject(String interpreterGroupId, AngularObject angularObject) {
40+
LOGGER.debug("Angular object updated in group {}: {}", interpreterGroupId,
41+
angularObject.getName());
42+
}
43+
44+
@Override
45+
public void onRemoveAngularObject(String interpreterGroupId, AngularObject angularObject) {
46+
LOGGER.debug("Angular object removed in group {}: {}", interpreterGroupId,
47+
angularObject.getName());
48+
}
49+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.zeppelin.notebook.cli;
18+
19+
import org.apache.zeppelin.helium.ApplicationEventListener;
20+
import org.apache.zeppelin.helium.HeliumPackage;
21+
import org.apache.zeppelin.interpreter.InterpreterResult;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
25+
/**
26+
* Headless implementation of {@link ApplicationEventListener}. Helium applications are out of
27+
* scope for headless note execution (no UI to render them into), so this only logs at debug
28+
* level.
29+
*/
30+
public class HeadlessApplicationEventListener implements ApplicationEventListener {
31+
32+
private static final Logger LOGGER =
33+
LoggerFactory.getLogger(HeadlessApplicationEventListener.class);
34+
35+
@Override
36+
public void onOutputAppend(String noteId, String paragraphId, int index, String appId,
37+
String output) {
38+
LOGGER.debug("Helium app {} output append for note {} paragraph {}", appId, noteId,
39+
paragraphId);
40+
}
41+
42+
@Override
43+
public void onOutputUpdated(String noteId, String paragraphId, int index, String appId,
44+
InterpreterResult.Type type, String output) {
45+
LOGGER.debug("Helium app {} output updated for note {} paragraph {}", appId, noteId,
46+
paragraphId);
47+
}
48+
49+
@Override
50+
public void onLoad(String noteId, String paragraphId, String appId, HeliumPackage pkg) {
51+
LOGGER.debug("Helium app {} loaded for note {} paragraph {}", appId, noteId, paragraphId);
52+
}
53+
54+
@Override
55+
public void onStatusChange(String noteId, String paragraphId, String appId, String status) {
56+
LOGGER.debug("Helium app {} status changed to {} for note {} paragraph {}", appId, status,
57+
noteId, paragraphId);
58+
}
59+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.zeppelin.notebook.cli;
18+
19+
import org.apache.zeppelin.notebook.Note;
20+
import org.apache.zeppelin.notebook.NoteEventListener;
21+
import org.apache.zeppelin.notebook.Paragraph;
22+
import org.apache.zeppelin.scheduler.Job;
23+
import org.apache.zeppelin.user.AuthenticationInfo;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
/**
28+
* Headless implementation of {@link NoteEventListener}. {@link #onParagraphStatusChange} is
29+
* printed to stdout so a blocking CLI run shows per-paragraph progress; the rest are debug-only
30+
* since there is no UI/index to notify in a headless run.
31+
*/
32+
public class HeadlessNoteEventListener implements NoteEventListener {
33+
34+
private static final Logger LOGGER = LoggerFactory.getLogger(HeadlessNoteEventListener.class);
35+
36+
@Override
37+
public void onNoteRemove(Note note, AuthenticationInfo subject) {
38+
LOGGER.debug("Note removed: {}", note.getId());
39+
}
40+
41+
@Override
42+
public void onNoteCreate(Note note, AuthenticationInfo subject) {
43+
LOGGER.debug("Note created: {}", note.getId());
44+
}
45+
46+
@Override
47+
public void onNoteUpdate(Note note, AuthenticationInfo subject) {
48+
LOGGER.debug("Note updated: {}", note.getId());
49+
}
50+
51+
@Override
52+
public void onParagraphRemove(Paragraph p) {
53+
LOGGER.debug("Paragraph removed: {}", p.getId());
54+
}
55+
56+
@Override
57+
public void onParagraphCreate(Paragraph p) {
58+
LOGGER.debug("Paragraph created: {}", p.getId());
59+
}
60+
61+
@Override
62+
public void onParagraphUpdate(Paragraph p) {
63+
LOGGER.debug("Paragraph updated: {}", p.getId());
64+
}
65+
66+
@Override
67+
public void onParagraphStatusChange(Paragraph p, Job.Status status) {
68+
System.out.println("[" + p.getId() + "] " + status);
69+
}
70+
}

0 commit comments

Comments
 (0)