You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/12 05:15:43 UTC
(texera) 01/01: fix(pve): serve PVE requests under /api/pve and drop per-gateway rewrites (#6130)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-6130-e462f228e44e84668a7b2582dc2ba777717ff83a
in repository https://gitbox.apache.org/repos/asf/texera.git
commit df8f133b24fb932d207bb8b853fdaa14dd984b9f
Author: Xuan Gu <16...@users.noreply.github.com>
AuthorDate: Sat Jul 11 22:15:22 2026 -0700
fix(pve): serve PVE requests under /api/pve and drop per-gateway rewrites (#6130)
### What changes were proposed in this PR?
The single-node NGINX gateway (`bin/single-node/nginx.conf`) had no
route for the Python Virtual Environment (PVE) REST calls, so they fell
through to the catch-all `location /` and returned 404 from
`dashboard-service`. In the UI this surfaces as a *"Could not load
Python virtual environments"* popup on every Python UDF operator
selection.
The root cause is that the frontend requested `/pve/...` while the
backend (`PveResource`, `@Path("/pve")`, Jersey mounted under `/api/*`
on the coordinator) serves `/api/pve/...`, so every gateway had to carry
its own `/pve` to `/api/pve` rewrite rule, and the single-node config
was missing one.
Per review feedback, instead of adding the missing rewrite, this PR
removes the mismatch at the source: the frontend now builds PVE URLs
with `AppSettings.getApiEndpoint()` and requests `/api/pve/...`
directly. Every routing layer then becomes a plain routing entry with no
path rewriting:
- `bin/single-node/nginx.conf`: `location /api/pve/` passes through to
the coordinator
- `frontend/proxy.config.json`: `/api/pve` entry (ordered before
`/api`), `pathRewrite` removed
- `bin/k8s/templates/base/gateway/gateway-routes.yaml`: `/api/pve` joins
the existing dynamic-backend match list, the `URLRewrite` filter is
removed
The backend is unchanged. Net effect: the 404 is fixed, and the
per-gateway rewrite special-casing that allowed the three configs to
drift apart is gone.
### Any related issues, documentation, discussions?
Related to #5963 (PVE single-node configuration).
### How was this PR tested?
- `virtual-environment.service.spec.ts` passes with the new URLs (5
specs).
- On the single-node Docker stack: `GET /api/pve/pves?cuid=1` returns
200 JSON from the coordinator, and the old `/pve` path falls through to
the dashboard as expected.
- `helm template` renders the updated k8s route with the `URLRewrite`
filter removed.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 4.8, Claude Fable 5)
---------
Co-authored-by: Claude Fable 5 <no...@anthropic.com>
---
bin/k8s/templates/base/gateway/gateway-routes.yaml | 13 +------------
bin/single-node/nginx.conf | 7 +++++++
frontend/proxy.config.json | 13 +++++--------
.../power-button/computing-unit-selection.component.ts | 2 +-
.../virtual-environment.service.spec.ts | 9 +++++----
.../virtual-environment/virtual-environment.service.ts | 17 +++++++++--------
6 files changed, 28 insertions(+), 33 deletions(-)
diff --git a/bin/k8s/templates/base/gateway/gateway-routes.yaml b/bin/k8s/templates/base/gateway/gateway-routes.yaml
index ac8096aa21..935d38bb07 100644
--- a/bin/k8s/templates/base/gateway/gateway-routes.yaml
+++ b/bin/k8s/templates/base/gateway/gateway-routes.yaml
@@ -124,20 +124,9 @@ spec:
- path:
type: PathPrefix
value: /api/executions/result/export
- backendRefs:
- - group: gateway.envoyproxy.io
- kind: Backend
- name: texera-dynamic-backend
- - matches:
- path:
type: PathPrefix
- value: /pve
- filters:
- - type: URLRewrite
- urlRewrite:
- path:
- type: ReplacePrefixMatch
- replacePrefixMatch: /api/pve
+ value: /api/pve
backendRefs:
- group: gateway.envoyproxy.io
kind: Backend
diff --git a/bin/single-node/nginx.conf b/bin/single-node/nginx.conf
index 515a7016da..c5e500e667 100644
--- a/bin/single-node/nginx.conf
+++ b/bin/single-node/nginx.conf
@@ -81,6 +81,13 @@ http {
proxy_send_timeout 1d;
}
+ location /api/pve/ {
+ proxy_pass http://workflow-runtime-coordinator-service:8085;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ }
+
+ # nginx prefix locations match by longest prefix
location /api/ {
proxy_pass http://dashboard-service:8080;
proxy_set_header Host $host;
diff --git a/frontend/proxy.config.json b/frontend/proxy.config.json
index 7d68961d9b..c26a239b17 100755
--- a/frontend/proxy.config.json
+++ b/frontend/proxy.config.json
@@ -55,18 +55,15 @@
"secure": false,
"changeOrigin": true
},
- "/api": {
- "target": "http://localhost:8080",
+ "/api/pve": {
+ "target": "http://localhost:8085",
"secure": false,
"changeOrigin": false
},
- "/pve": {
- "target": "http://localhost:8085",
+ "/api": {
+ "target": "http://localhost:8080",
"secure": false,
- "changeOrigin": false,
- "pathRewrite": {
- "^/pve": "/api/pve"
- }
+ "changeOrigin": false
},
"/wsapi": {
"target": "http://localhost:8085",
diff --git a/frontend/src/app/workspace/component/power-button/computing-unit-selection.component.ts b/frontend/src/app/workspace/component/power-button/computing-unit-selection.component.ts
index fed31fddb4..b633e6eca3 100644
--- a/frontend/src/app/workspace/component/power-button/computing-unit-selection.component.ts
+++ b/frontend/src/app/workspace/component/power-button/computing-unit-selection.component.ts
@@ -134,7 +134,7 @@ export class ComputingUnitSelectionComponent implements OnInit {
// variables for creating a virtual environment
pves: PveDraft[] = [];
systemPackages: { name: string; version: string }[] = [];
- // True while a /pve/system response is in flight. The server resolves
+ // True while an /api/pve/system response is in flight. The server resolves
// the full pinned set with a `pip freeze` against a throwaway venv,
// which can take 30–60s on the first request after a server restart.
systemPackagesLoading = false;
diff --git a/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts b/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
index c194c115cc..c124972baf 100644
--- a/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
+++ b/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
@@ -18,6 +18,7 @@
*/
import { TestBed } from "@angular/core/testing";
+import { AppSettings } from "../../../common/app-setting";
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { UserPveRecord, WorkflowPveService } from "./virtual-environment.service";
import { commonTestProviders } from "../../../common/testing/test-utils";
@@ -45,7 +46,7 @@ describe("WorkflowPveService", () => {
expect(resp.veid).toBe(42);
});
- const req = httpTestingController.expectOne("/pve/db");
+ const req = httpTestingController.expectOne(`${AppSettings.getApiEndpoint()}/pve/db`);
expect(req.request.method).toBe("POST");
expect(req.request.body).toEqual({ name: "env-a", packages });
req.flush({ veid: 42 });
@@ -57,7 +58,7 @@ describe("WorkflowPveService", () => {
expect(resp.veid).toBe(7);
});
- const req = httpTestingController.expectOne("/pve/db/7");
+ const req = httpTestingController.expectOne(`${AppSettings.getApiEndpoint()}/pve/db/7`);
expect(req.request.method).toBe("PUT");
expect(req.request.body).toEqual({ name: "env-b", packages });
req.flush({ veid: 7 });
@@ -69,7 +70,7 @@ describe("WorkflowPveService", () => {
expect(resp).toEqual(records);
});
- const req = httpTestingController.expectOne("/pve/db");
+ const req = httpTestingController.expectOne(`${AppSettings.getApiEndpoint()}/pve/db`);
expect(req.request.method).toBe("GET");
req.flush(records);
});
@@ -77,7 +78,7 @@ describe("WorkflowPveService", () => {
it("deleteUserPve() DELETEs /pve/db/{veid}", () => {
service.deleteUserPve(9).subscribe();
- const req = httpTestingController.expectOne("/pve/db/9");
+ const req = httpTestingController.expectOne(`${AppSettings.getApiEndpoint()}/pve/db/9`);
expect(req.request.method).toBe("DELETE");
req.flush(null);
});
diff --git a/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.ts b/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.ts
index d399f643a0..cc4d7df942 100644
--- a/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.ts
+++ b/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.ts
@@ -21,6 +21,7 @@ import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { HttpClient, HttpParams } from "@angular/common/http";
import { AuthService } from "../../../common/service/user/auth.service";
+import { AppSettings } from "../../../common/app-setting";
export interface PackageResponse {
system: string[];
@@ -42,19 +43,19 @@ export class WorkflowPveService {
constructor(private http: HttpClient) {}
savePve(name: string, packages: Record<string, string>): Observable<{ veid: number }> {
- return this.http.post<{ veid: number }>("/pve/db", { name, packages });
+ return this.http.post<{ veid: number }>(`${AppSettings.getApiEndpoint()}/pve/db`, { name, packages });
}
updateUserPve(veid: number, name: string, packages: Record<string, string>): Observable<{ veid: number }> {
- return this.http.put<{ veid: number }>(`/pve/db/${veid}`, { name, packages });
+ return this.http.put<{ veid: number }>(`${AppSettings.getApiEndpoint()}/pve/db/${veid}`, { name, packages });
}
listUserPves(): Observable<UserPveRecord[]> {
- return this.http.get<UserPveRecord[]>("/pve/db");
+ return this.http.get<UserPveRecord[]>(`${AppSettings.getApiEndpoint()}/pve/db`);
}
deleteUserPve(veid: number): Observable<void> {
- return this.http.delete<void>(`/pve/db/${veid}`);
+ return this.http.delete<void>(`${AppSettings.getApiEndpoint()}/pve/db/${veid}`);
}
getAccessToken(): string | null {
@@ -73,12 +74,12 @@ export class WorkflowPveService {
getSystemPackages(cuid: number): Observable<PackageResponse> {
const params = this.buildBaseParams().set("cuid", cuid.toString());
- return this.http.get<PackageResponse>("/pve/system", { params });
+ return this.http.get<PackageResponse>(`${AppSettings.getApiEndpoint()}/pve/system`, { params });
}
fetchPVEs(cuid: number): Observable<PvePackageResponse[]> {
const params = this.buildBaseParams().set("cuid", cuid.toString());
- return this.http.get<PvePackageResponse[]>("/pve/pves", { params });
+ return this.http.get<PvePackageResponse[]>(`${AppSettings.getApiEndpoint()}/pve/pves`, { params });
}
getUserPackages(cuid: number, pveName: string): Observable<string[]> {
@@ -86,14 +87,14 @@ export class WorkflowPveService {
}
deleteEnvironments(cuid: number) {
- return this.http.delete(`/pve/pves/${cuid}`);
+ return this.http.delete(`${AppSettings.getApiEndpoint()}/pve/pves/${cuid}`);
}
deletePackage(cuid: number, pveName: string, packageName: string) {
const params = this.buildBaseParams();
return this.http.delete<string[]>(
- `/pve/${cuid}/${encodeURIComponent(pveName)}/packages/${encodeURIComponent(packageName)}`,
+ `${AppSettings.getApiEndpoint()}/pve/${cuid}/${encodeURIComponent(pveName)}/packages/${encodeURIComponent(packageName)}`,
{ params }
);
}