onceMisery commented on issue #25235:
URL: https://github.com/apache/pulsar/issues/25235#issuecomment-3976255362
Thank you for your reply. Here is my implementation plan. Which one do you
prefer?
### Approach B: Server-Side Aggregation (New API Endpoint)
Add a new REST endpoint that returns function names with status summaries.
The CLI makes a single API call for both
filtering and extended output.
#### Implementation Steps
**Step 1: Add DTO data model**
File: `pulsar-client-admin-api/.../policies/data/FunctionSummary.java` (new)
```java
@Data
public class FunctionSummary {
private String name;
private String state; // RUNNING, STOPPED, PARTIAL
private int numInstances;
private int numRunning;
}
```
**Step 2: Extend Admin API interface**
File: `pulsar-client-admin-api/.../Functions.java`
```java
/**
* Get the list of functions with status summary under a namespace.
*/
List<FunctionSummary> getFunctionsWithStatus(String tenant, String namespace)
throws PulsarAdminException;
CompletableFuture<List<FunctionSummary>> getFunctionsWithStatusAsync(
String tenant, String namespace);
```
**Step 3: Implement Admin API client**
File: `pulsar-client-admin/.../FunctionsImpl.java`
```java
@Override
public List<FunctionSummary> getFunctionsWithStatus(String tenant, String
namespace)
throws PulsarAdminException {
return sync(() -> getFunctionsWithStatusAsync(tenant, namespace));
}
@Override
public CompletableFuture<List<FunctionSummary>> getFunctionsWithStatusAsync(
String tenant, String namespace) {
WebTarget path =
functions.path(tenant).path(namespace).path("status-summary");
return asyncGetRequest(path, new GenericType<List<FunctionSummary>>() {
});
}
```
New REST path: `GET /admin/v3/functions/{tenant}/{namespace}/status-summary`
**Step 4: Add REST endpoint**
File: `pulsar-functions/worker/.../v3/FunctionsApiV3Resource.java`
```java
@GET
@ApiOperation(
value = "Displays the list of Pulsar Functions with status summary",
response = FunctionSummary.class,
responseContainer = "List"
)
@Produces(MediaType.APPLICATION_JSON)
@Path("/{tenant}/{namespace}/status-summary")
public List<FunctionSummary> listFunctionsWithStatus(
final @PathParam("tenant") String tenant,
final @PathParam("namespace") String namespace) {
return functions().listFunctionsWithStatus(
tenant, namespace, uri.getRequestUri(), authParams());
}
```
**Step 5: Implement Worker service logic**
File: `pulsar-functions/worker/.../ComponentImpl.java`
```java
public List<FunctionSummary> listFunctionsWithStatus(
final String tenant, final String namespace,
final URI uri, final AuthenticationParameters authParams) {
// impl listFunctionsWithStatus
}
```
> Note: `getComponentStatus()` is an existing method in `ComponentImpl` that
handles inter-worker RPCs internally. By
> calling it server-side, we avoid N external HTTP round-trips.
**Step 6: Modify CLI command**
File: `pulsar-client-tools/.../CmdFunctions.java` — `ListFunctions` class
```java
@Command(description = "List all Pulsar Functions running under a specific
tenant and namespace")
class ListFunctions extends NamespaceCommand {
@Option(names = "--state", description = "Filter by state: RUNNING,
STOPPED, PARTIAL")
private String state;
@Option(names = {"-l", "--long"}, description = "Show extended output
with status")
private boolean longFormat;
@Override
void runCmd() throws Exception {
List<String> functions = getAdmin().functions().getFunctions(tenant,
namespace);
if (state == null && !longFormat) {
print(functions); // Original behavior unchanged
return;
}
for (String fn : functions) {
FunctionStatus status = getAdmin().functions()
.getFunctionStatus(tenant, namespace, fn);
String fnState = deriveState(status);
if (state != null && !state.equalsIgnoreCase(fnState)) {
continue;
}
if (longFormat) {
System.out.printf("%-30s %-10s %d/%d%n",
fn, fnState, status.numRunning, status.numInstances);
} else {
print(fn);
}
}
}
}
```
---
### Approach C: Hybrid (Server Batch Retrieval + Client-Side Filtering) —
Recommended
Server provides a single batch API that returns all functions with their
status summaries. The CLI consumes this API and
handles all filtering (`--state`) and formatting (`--long`) logic on the
client side.
**Key difference from Approach B:** Approach B frames the server as the
"aggregation + filtering" layer. Approach C
explicitly separates concerns — the server is a **data provider** only
(batch status retrieval), while the client owns *
*all presentation logic** (filtering, sorting, formatting). This keeps the
server endpoint generic and reusable.
#### Architecture
```
┌─────────────────────────────────────────────────────┐
│ CLI (Client) │
│ ┌───────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ --state │──▶│ Filter │──▶│ Print (plain │ │
│ │ --long │ │ by state │ │ or table fmt) │ │
│ └───────────┘ └──────────┘ └───────────────┘ │
│ ▲ │
│ │ Single API call │
└────────┼────────────────────────────────────────────┘
│
┌────────┼────────────────────────────────────────────┐
│ Server (Worker) │
│ ┌─────────────────────────────────────────────┐ │
│ │ GET /status-summary │ │
│ │ → list metadata → batch getComponentStatus │ │
│ │ → return List<FunctionSummary> (ALL funcs) │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
#### Implementation Steps
**Step 1: Add DTO data model** (same as Approach B)
**Step 2: Extend Admin API interface** (same as Approach B)
**Step 3: Implement Admin API client** (same as Approach B)
**Step 4: Add REST endpoint** (same as Approach B)
**Step 5: Implement Worker service logic** (same as Approach B)
**Step 6: Modify CLI command — filtering and formatting on client side**
File: `pulsar-client-tools/.../CmdFunctions.java` — `ListFunctions` class
This is where Approach C differs from B: **all filtering and formatting
logic lives in the CLI**, not on the server.
```java
@Command(description = "List all Pulsar Functions running under a specific
tenant and namespace")
class ListFunctions extends NamespaceCommand {
@Option(names = "--state",
description = "Filter by state: RUNNING, STOPPED, PARTIAL")
private String state;
@Option(names = {"-l", "--long"},
description = "Show extended output with status")
private boolean longFormat;
@Override
void runCmd() throws Exception {
// No flags → original behavior, original API
if (state == null && !longFormat) {
print(getAdmin().functions().getFunctions(tenant, namespace));
return;
}
// Single batch call to get all summaries
List<FunctionSummary> summaries = getAdmin().functions()
.getFunctionsWithStatus(tenant, namespace);
// Client-side filtering
if (state != null) {
summaries = summaries.stream()
.filter(s -> state.equalsIgnoreCase(s.getState()))
.collect(Collectors.toList());
}
// Client-side formatting
if (longFormat) {
System.out.printf("%-30s %-10s %s%n",
"NAME", "STATE", "INSTANCES");
for (FunctionSummary s : summaries) {
System.out.printf("%-30s %-10s %d/%d%n",
s.getName(), s.getState(),
s.getNumRunning(), s.getNumInstances());
}
} else {
summaries.forEach(s -> print(s.getName()));
}
}
}
```
## 5. Side-by-Side Comparison
| Dimension | B: Server-Side | C: Hybrid
(Recommended) |
|-------------------------------|------------------------|-------------------------|
| New API endpoints | 1 | 1
|
| Files changed | 7 | 7
|
| Network calls (client→server) | 1 | 1
|
| Status aggregation | Worker | Worker
|
| Filtering location | Server | **CLI**
|
| Formatting location | Server | **CLI**
|
| Non-CLI clients benefit | Yes | Yes
|
| Server endpoint reusability | Coupled to CLI needs | **Generic**
|
| Performance (100 functions) | 1 HTTP + internal RPCs | 1 HTTP + internal
RPCs |
| Backward compatibility | Full | Full
|
| Complexity | Medium | Medium
|
## 6. Recommendation
**Approach C (Hybrid) is recommended** for the following reasons:
1. **Clean separation of concerns** — Server is a pure data provider; CLI
owns all presentation logic. This makes the
API reusable for other consumers with different filtering/formatting
needs.
2. **Performance** — Server-side `getComponentStatus()` uses internal worker
RPCs, far faster than N external HTTP calls
from the client.
3. **Extensibility** — Adding new CLI filters (e.g., `--name-pattern`)
requires zero server changes.
4. **Precedent** — Consistent with Pulsar's existing API patterns (dedicated
endpoints for enriched data).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]