-
Notifications
You must be signed in to change notification settings - Fork 360
feat(rbac): Netty Proxy & Sticky Lease Manager for Scalable Multi-Tenant Task Workers #16203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
shruthi713
wants to merge
55
commits into
develop
Choose a base branch
from
rbac-netty-proxy-poc
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 43 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
c9f58ba
feat(rbac-poc): implement standalone Task Manager Service and integra…
sidhdirenge 03d9761
feat(rbac-poc): pass namespace as routingKey in RemoteTaskExecutor
sidhdirenge 94f9079
feat(rbac-poc): integrate StickyLeaseManager on Task Worker side
sidhdirenge 375876b
feat(rbac-poc): refactor TaskManagerMain to extend AbstractServiceMai…
sidhdirenge 5b162ec
feat(sticky-leases): implement randomized tie-breaking fallback and m…
sidhdirenge 912ce60
feat: Implement Netty Proxy streaming skeleton for Task Manager POC
shruthi713 3d94f45
feat(poc): Implement Netty Proxy and Gatekeeper IAM reaper
shruthi713 3de3809
feat(poc): Wire up App Fabric client to Netty Proxy
shruthi713 92430a8
feat(poc): Wire up Netty Proxy to K8s Discovery service
shruthi713 f4c54eb
feat(poc): Inject ground truth occupancy headers into Task Worker res…
shruthi713 45555ad
chore: update logging prefix from sidhdirenge to shruzard
shruthi713 26a1d1b
chore: add comprehensive logging across Netty POC components with shr…
shruthi713 3598302
refactor(poc): eliminate legacy task confirmation network callbacks
shruthi713 6a4f79c
refactor
shruthi713 f9045ab
refactor: Add Predictive Proxy Expiration and Phase 0/1 Code Cleanup
shruthi713 fda268d
feat: Reinject 10-minute security cache eviction boundary for Worker
shruthi713 422a009
feat: Reinject 10-minute security cache eviction boundary for Worker
shruthi713 8c44f62
rat test
shruthi713 204fc19
fix: Add license headers and remove dangling try block
shruthi713 dfe1e85
fix: Remove dangling try block
shruthi713 50ed55b
chore: Remove deprecated TaskManagerTest
shruthi713 b4d55ff
fix(checkstyle): wrap lines exceeding 120 chars in cdap-common
shruthi713 0be04ef
fix(checkstyle): wrap lines exceeding 120 chars in cdap-common
shruthi713 d39de87
chore: Route RemoteTaskExecutor traffic to task.manager via Constants
shruthi713 9d72f2c
k8s changes
shruthi713 e31d6bc
add logs, update activeTasks only after last response
shruthi713 a42d334
fix checkstyle
shruthi713 2bbd302
fix(netty-proxy): gracefully drain request body on 429 rejection to p…
shruthi713 27604c0
fix(netty-proxy): gracefully drain request body on 429 rejection to p…
shruthi713 9570164
remove hostname fallback
shruthi713 5d3f335
feat(rbac): increase task worker retry timeout to 90s and prevent fal…
shruthi713 b5c613f
remove hostname fallback
shruthi713 8f82d4f
Merge branch 'rbac-netty-proxy-poc-agent' into rbac-netty-proxy-poc
shruthi713 7367018
fix(rbac): correct configuration key for fallback timeout in RemoteTa…
shruthi713 746118a
test(rbac): hardcode fallback timeout to 90s for testing
shruthi713 ea00d91
fix(rbac): prevent occupancy leak on connection closures in ProxyBack…
shruthi713 9513533
feat(discovery): add live Kubernetes Endpoints watcher to KubeDiscove…
shruthi713 592c826
fix(netty-proxy): restore SSL support for outbound TaskWorker connect…
shruthi713 73c485b
feat(discovery): preserve pre-warmed discoverables, SSL, and add live…
shruthi713 08aa1a2
refactor(proxy): encapsulate endpoints watcher enablement in ProxyFro…
shruthi713 622ba01
fix(checkstyle): wrap log line exceeding 120 chars in ProxyFrontendHa…
shruthi713 1d4991a
refactor(retry): change fallback timeout and max retry time from 90s …
shruthi713 2966cff
feat(retry): include SocketException and transport failures in Remote…
shruthi713 e759b2d
docs: add comprehensive English architectural comments across proxy, …
shruthi713 31fb471
fix(proxy): selectively sync ground truth headers only on 409/429 rej…
shruthi713 8a68d01
refactor(proxy): initialize PodState lastActivityTime cleanly with Sy…
shruthi713 bf5a38e
chore: remove Dockerfile from PR branch
shruthi713 fd89a7c
chore: remove _agents/rules/rbac_taskmanager.md from PR branch
shruthi713 0a2e223
chore: remove _agents/rules/task_manager_context.md from PR branch
shruthi713 70f8bc6
perf(k8s): eliminate blocking synchronous readNamespacedEndpoints cal…
shruthi713 27c7b45
refactor(k8s): simplify EndpointsWatcherThread start check to only ch…
shruthi713 4d9c301
refactor: rename TaskManagerMain to TaskManagerServiceMain
shruthi713 dd5e7fb
make TaskManager register itself to KubeDiscoverService
shruthi713 2eb4741
update PodState to use AtomicReference and CAS mechanism for lock fre…
shruthi713 3482e23
update logs
shruthi713 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Copyright © 2026 Cask Data, Inc. | ||
| # | ||
| # Licensed 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. | ||
|
|
||
| FROM us-east1-docker.pkg.dev/j145774183a931adb-tp/cdf-dev-shru/cloud-data-fusion:latest | ||
| # For OSS CDAP, use "FROM gcr.io/cdapio/cdap:latest" | ||
|
|
||
| RUN rm -rf /opt/cdap/master/lib/io.cdap.cdap.cdap-common-6.12.0-SNAPSHOT.jar | ||
|
|
||
| COPY cdap-common/target/cdap-common-6.12.0-SNAPSHOT.jar /opt/cdap/master/lib/io.cdap.cdap.cdap-common-6.12.0-SNAPSHOT.jar | ||
|
|
||
| RUN rm -rf /opt/cdap/master/ext/environments/k8s/io.cdap.cdap.cdap-kubernetes-6.12.0-SNAPSHOT.jar | ||
|
|
||
|
|
||
| COPY cdap-kubernetes/target/cdap-kubernetes-6.12.0-SNAPSHOT.jar /opt/cdap/master/ext/environments/k8s/io.cdap.cdap.cdap-kubernetes-6.12.0-SNAPSHOT.jar | ||
|
|
||
|
|
||
| RUN chmod -R 755 /opt/cdap |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # CDAP RBAC Warm Sticky Leases & Task Manager Service | ||
|
|
||
| When working on the RBAC Everywhere feature, Namespaced Service Accounts (NSA), or Task Worker pod scaling in this repository: | ||
|
|
||
| 1. **Architecture Context**: | ||
| * Refer to the detailed Warm Sticky Lease research notes here: | ||
| [rbac_taskmanager_research_notes.md](file:///usr/local/google/home/venkataramansh/.gemini/jetski/brain/2099d0c8-9e2b-4db5-a81a-5cfb437c1660/rbac_taskmanager_research_notes.md) | ||
| * This feature resolves the "429 collision storm" and cold-start latencies (~40s) by shifting tenant isolation from the request level to the namespace/pod lease level. | ||
|
|
||
| 2. **Core Routing Rules**: | ||
| * **Direct Routing**: `RemoteClient` must bypass K8s round-robin load balancing. It resolves the headless task-worker service via DNS expansion to individual pod IPs, queries the `TaskManager` service to resolve the lease, and routes directly to the leased pod IP. | ||
| * **Lease Registry**: The `TaskManager` service holds the lease maps in-memory to prevent Spanner database write contention. It uses a `ReentrantLock` to serialize checks/actions and prevent race conditions. | ||
| * **Local Guard**: Individual Task Workers use `StickyLeaseManager` as a fail-safe to reject mismatching namespace requests locally with a `429`. | ||
|
|
||
| 3. **Key Classes**: | ||
| * App Fabric / Client: `RemoteClient`, `RemoteTaskExecutor`, `KubeDiscoveryService` | ||
| * Task Manager: `TaskManager`, `TaskManagerHttpHandler`, `TaskManagerService`, `TaskManagerMain` | ||
| * Task Worker: `StickyLeaseManager`, `TaskWorkerHttpHandlerInternal` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Task Manager & Sticky Lease Context | ||
|
|
||
| ## Architecture | ||
| * **Environment:** Cloud Data Fusion (CDF) on GKE (ZooKeeper-free). | ||
| * **Service Discovery:** Headless DNS expansion to pod IPs. | ||
| * **Coordination:** Standalone single-replica HTTP `TaskManager` service. | ||
| * **Local Guard:** `StickyLeaseManager` on Task Worker pods enforcing "First-Write Wins" lease lock. | ||
|
|
||
| ## Concurrency & Workload Limits | ||
| * **Concurrency:** Max 10 concurrent tasks per pod (enforced by `podActiveTaskCounts` / `activeTasks`). | ||
| * **Lifetime Limit:** Max 10 tasks before reset (enforced by `podTotalTaskProcessedCounts`). | ||
| * *Increment in `resolve`:* Enforces strict limit of 10 tasks started (safe, current behavior). | ||
| * *Increment in `finish`:* Allows better utilization but pod can process up to 19 tasks due to concurrency. | ||
|
|
||
| ## Reliability & Recovery | ||
| * **Downtime:** `RemoteClient` falls back to local consistent hashing if `TaskManager` is down. | ||
| * **State Recovery:** To recover from `TaskManager` restarts without polling, use a self-correction pattern: | ||
| * Task Worker returns `409 Conflict` (with active namespace in body) on lease mismatch. | ||
| * `RemoteClient` parses 409 and notifies `TaskManager` to update its lease map. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/PodState.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /* | ||
| * Copyright © 2026 Cask Data, Inc. | ||
| * | ||
| * Licensed 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 io.cdap.cdap.common.internal.remote; | ||
|
|
||
| /** | ||
| * Tracks the routing state and load for a given worker pod IP. | ||
| */ | ||
| public class PodState { | ||
| private String leasedNamespace; | ||
| private int inflightRequests; | ||
| private long lastActivityTime; | ||
|
|
||
| public PodState(String leasedNamespace, int inflightRequests) { | ||
| this.leasedNamespace = leasedNamespace; | ||
| this.inflightRequests = inflightRequests; | ||
| // Subtract 40 seconds worth of nanos to instantly trigger predictions on boot | ||
| this.lastActivityTime = System.nanoTime() | ||
| - java.util.concurrent.TimeUnit.SECONDS.toNanos(40); | ||
| } | ||
|
|
||
| public String getLeasedNamespace() { | ||
| return leasedNamespace; | ||
| } | ||
|
|
||
| public void setLeasedNamespace(String leasedNamespace) { | ||
| this.leasedNamespace = leasedNamespace; | ||
| } | ||
|
|
||
| public int getInflightRequests() { | ||
| return inflightRequests; | ||
| } | ||
|
|
||
| public void setInflightRequests(int inflightRequests) { | ||
| this.inflightRequests = inflightRequests; | ||
| } | ||
|
|
||
| public long getLastActivityTime() { | ||
| return lastActivityTime; | ||
| } | ||
|
|
||
| public void setLastActivityTime(long lastActivityTime) { | ||
| this.lastActivityTime = lastActivityTime; | ||
| } | ||
| } | ||
126 changes: 126 additions & 0 deletions
126
cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/ProxyBackendHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| /* | ||
| * Copyright © 2026 Cask Data, Inc. | ||
| * | ||
| * Licensed 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 io.cdap.cdap.common.internal.remote; | ||
|
|
||
| import io.netty.channel.Channel; | ||
| import io.netty.channel.ChannelFutureListener; | ||
| import io.netty.channel.ChannelHandlerContext; | ||
| import io.netty.channel.ChannelInboundHandlerAdapter; | ||
| import io.netty.handler.codec.http.HttpResponse; | ||
|
|
||
| import java.util.Map; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class ProxyBackendHandler extends ChannelInboundHandlerAdapter { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(ProxyBackendHandler.class); | ||
|
|
||
|
|
||
| private final Channel inboundChannel; | ||
| private final Map<String, PodState> podRegistry; | ||
| private final String targetWorkerAddress; | ||
| private boolean decremented = false; | ||
|
|
||
| public ProxyBackendHandler(Channel inboundChannel, Map<String, PodState> podRegistry, String targetWorkerAddress) { | ||
| this.inboundChannel = inboundChannel; | ||
| this.podRegistry = podRegistry; | ||
| this.targetWorkerAddress = targetWorkerAddress; | ||
| } | ||
|
|
||
| private synchronized void decrementInflight() { | ||
| if (!decremented) { | ||
| PodState state = podRegistry.get(targetWorkerAddress); | ||
| if (state != null) { | ||
| synchronized (state) { | ||
| state.setInflightRequests(Math.max(0, state.getInflightRequests() - 1)); | ||
| state.setLastActivityTime(System.nanoTime()); | ||
| } | ||
| } | ||
| decremented = true; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void channelRead(ChannelHandlerContext ctx, Object msg) { | ||
| if (msg instanceof HttpResponse) { | ||
| HttpResponse resp = (HttpResponse) msg; | ||
| PodState state = podRegistry.get(targetWorkerAddress); | ||
| if (state != null) { | ||
| // Thread-safe update from Worker Ground Truth headers | ||
| synchronized (state) { | ||
| String activeTasksStr = resp.headers().get("X-Active-Tasks"); | ||
| String leasedNamespace = resp.headers().get("X-Leased-Namespace"); | ||
| // Treat Task Worker as strict source of truth for load ONLY if it rejects us | ||
| if (resp.status().code() == 429 || resp.status().code() == 409) { | ||
| if (activeTasksStr != null) { | ||
| state.setInflightRequests(Integer.parseInt(activeTasksStr)); | ||
| } | ||
| } | ||
|
|
||
| if (leasedNamespace != null) { | ||
| state.setLeasedNamespace(leasedNamespace); | ||
| } | ||
|
|
||
| state.setLastActivityTime(System.nanoTime()); | ||
|
|
||
| if (activeTasksStr != null || leasedNamespace != null) { | ||
| LOG.info("shruzard - ProxyBackendHandler: Header Sync " | ||
| + "PodState for {}. Local Occupancy: {}, Remote Tasks: {}, Namespace: {}", | ||
| targetWorkerAddress, state.getInflightRequests(), activeTasksStr, | ||
| state.getLeasedNamespace()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (msg instanceof io.netty.handler.codec.http.LastHttpContent) { | ||
| decrementInflight(); | ||
| } | ||
|
|
||
| // Forward worker responses directly back to the client | ||
| inboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { | ||
| if (future.isSuccess()) { | ||
| ctx.channel().read(); | ||
| } else { | ||
| future.channel().close(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public void channelWritabilityChanged(ChannelHandlerContext ctx) { | ||
| // Backend Worker channel is saturated; pause reading from App Fabric client | ||
| if (inboundChannel != null && inboundChannel.isActive()) { | ||
| inboundChannel.config().setAutoRead(ctx.channel().isWritable()); | ||
| } | ||
| ctx.fireChannelWritabilityChanged(); | ||
| } | ||
|
|
||
| @Override | ||
| public void channelInactive(ChannelHandlerContext ctx) { | ||
| decrementInflight(); | ||
| ProxyFrontendHandler.closeOnFlush(inboundChannel); | ||
| } | ||
|
|
||
| @Override | ||
| public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { | ||
| decrementInflight(); | ||
| cause.printStackTrace(); | ||
| ProxyFrontendHandler.closeOnFlush(ctx.channel()); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid blocking the Netty event loop threads with synchronized blocks in
ProxyFrontendHandlerandProxyBackendHandler, refactorPodStateto be completely lock-free. This can be achieved by using an immutableStaterecord/class and updating it atomically viaAtomicReference.