Skip to content

[ZEPPELIN-5745] Add headless CLI to run a note without starting the server - #5461

Open
HwangRock wants to merge 2 commits into
apache:masterfrom
HwangRock:ZEPPELIN-5745-run-note-headless
Open

[ZEPPELIN-5745] Add headless CLI to run a note without starting the server#5461
HwangRock wants to merge 2 commits into
apache:masterfrom
HwangRock:ZEPPELIN-5745-run-note-headless

Conversation

@HwangRock

@HwangRock HwangRock commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

What is this PR for?

Today a note can only be run through a live server — you start the web server
(Jetty/REST/WebSocket) and drive it from the browser, or call the REST API of an
already-running instance. That ties every execution to a long-lived server
process, which is awkward for cron jobs, CI checks, and batch pipelines.

This PR adds a small CLI, bin/run-note.sh, that runs a single note headlessly
— no web server at all — in the spirit of papermill
for Jupyter. It assembles just the interpreter runtime, executes the note, saves
the result, and exits.

Usage

bin/run-note.sh -i <notePath> [-o <outputPath>] [-p <key> <value>]...
  • -i — logical path of the note to run (e.g. /Demo, as shown in the note list). Required.
  • -o — save the executed note to a new path instead of overwriting the input.
  • -p — inject a parameter, substituted into ${key} / ${key=default} placeholders in the note. Repeatable.

Example:

bin/run-note.sh -i "/Python Tutorial/1. IPython Basic" -p date 2026-09-05 -o /out/report

How it works

NotebookRunner builds the notebook + interpreter runtime with manual dependency
injection (the same recipe the interpreter tests already use), so no Jetty/HK2 is
involved. The interpreter event server is still started — interpreters run as
separate processes and need it to call results back — but the web layer is not.
Paragraph output streams to stdout as the note runs, and the executed note is
saved through the normal NotebookRepo.

Behavior worth calling out

  • Exit code: a paragraph left in a non-FINISHED state (error, abort, or
    skipped after an earlier failure) fails the run with a non-zero exit, so CI/batch
    callers can detect failures; a clean run exits 0.
  • Clean shutdown: a one-shot CLI must not inherit the server's "run forever"
    assumption. On exit the process stops the interpreter processes, each
    RemoteScheduler's own thread pool, the event server, and ExecutorFactory's
    pools, then calls System.exit() as a backstop so a stray non-daemon thread
    cannot hang the JVM.
  • stdout vs stderr: paragraph output goes to stdout (so > file captures only
    the results); the "saved / finished" summary goes to stderr.

Known limitation

Notes that drive other paragraphs programmatically via z.run() / z.runNote()
are not supported yet — that path executes paragraphs asynchronously and can
race the run's completion check. Straight top-to-bottom notes (the common batch
case) are unaffected. This is noted in HeadlessProcessListener and can be a
follow-up.

What type of PR is it?

Feature

What is the Jira issue?

How should this be tested?

Automated — 19 unit/integration tests under notebook/cli/, including two that
launch bin/run-note.sh in a real separate JVM and assert the process exits
within a timeout (guarding against the JVM-hang class of bug) with the correct exit
code (0 on success, 1 on a failed paragraph):

mvn -pl zeppelin-server -am test -Dtest='NotebookRunner*Test'

Manual:

bin/run-note.sh -i /Demo

runs a note end to end, prints its output, saves the result, and returns to the
shell prompt on its own.

Screenshots (if appropriate)

2026-09-05.10.10.53.mov

Questions:

  • Does the license files need to update? No — no new dependencies are introduced.
  • Is there breaking changes for older versions? No — only new files are added; no existing code is modified.
  • Does this needs documentation? A short docs page for run-note.sh would help and can follow.

…erver

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.
@HwangRock
HwangRock force-pushed the ZEPPELIN-5745-run-note-headless branch from a2a6180 to 1806fe2 Compare September 5, 2026 13:18
… tree

NotebookRunnerIntegrationTest counted RemoteInterpreterServer JVMs
machine-wide via `jps -l`, so any concurrent zeppelin-server test that
spawned its own interpreter made the orphan assertion see non-zero
survivors (expected:0 but was:8 in the core-modules CI job). Poll the
descendant pids of the run-note.sh process while it is alive and assert
none of them survive the parent exit, scoping the check to this test's
own process tree.
@jongyoul

jongyoul commented Sep 6, 2026

Copy link
Copy Markdown
Member

@HwangRock Could you please explain why this feature is needed? E.g. specific usecases? No server exeuction looks good, but Zeppelin focuses on manaing lifecycle for interpreters. Without Zeppelin server, we cannot do it. Am I understanding correctly?

@HwangRock

HwangRock commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for good questions @jongyoul .

why this feature is needed? E.g. specific usecases?

Right now a note can only be run on a live server. You either drive it from the browser or call the REST API of a running instance, so every run is tied to a long-lived server.

That's awkward for batch/CI. Think of a cron job, a CI check, or an Airflow task that starts one container,
runs a single note, and checks the exit code.
Standing up a full Jetty/REST/WebSocket server just to run one note is a lot of overhead. It's basically what papermill does for Jupyter.

Some earlier context:

  • ZEPPELIN-4619 (2020) added --run, but that boots the whole server before running the note, so it's still heavy for batch. ZEPPELIN-5745 (2022) then asked to run a note without starting the server, which is what this PR does.
  • nteract/papermill#206 is still open, asking to run Zeppelin notes through papermill.

So the goal is a lightweight way to run a single note without a server, for batch/CI, using the exit code to tell success from failure.

Without Zeppelin server, we cannot do it. Am I understanding correctly?

The interpreter lifecycle isn't handled by Jetty (the web server) itself, but by the InterpreterSettingManager object.
So when the CLI runs a note, it builds that object just for that single run and manages the lifecycle through it, as shown in the sequence diagram below.


zeppelin2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants