Files
temporal/api/adminservicemock/v1
Veeral Patel 94556cc8ce feat: add GetTaskQueueUserData RPC to admin service (#9934)
## What changed?

This PR adds a new `GetTaskQueueUserData` RPC to the admin service.

Given a namespace, task queue name, task queue type, and optional
partition ID (default is 0, which is `root`), it returns the user data
currently loaded by that partition.

This PR wraps the existing `GetTaskQueueUserData` RPC in Matching
Service. We will also create a `tdbg` command which calls this Admin
Service RPC, in a separate PR.

## Why?

Each task queue family has associated metadata, stored in
[TaskQueueUserData](https://github.com/temporalio/temporal/blob/main/proto/internal/temporal/server/api/persistence/v1/task_queues.proto).
Metadata related to worker versioning, queue rate limiting, fairness are
all stored in `TaskQueueUserData`.

TaskQueueUserData is replicated from the root partition to all other
partitions.

However, there is no admin-accessible way to read the user data loaded
by a specific partition or compare versions across partitions to
diagnose replication lag.

## Files changed

| File | Change |
|---|---|
| `proto/internal/.../adminservice/v1/request_response.proto` | Added
`GetTaskQueueUserDataRequest` and `GetTaskQueueUserDataResponse`
messages |
| `proto/internal/.../adminservice/v1/service.proto` | Added
`GetTaskQueueUserData` RPC to `AdminService` |
| `service/frontend/admin_handler.go` | Implemented
`AdminHandler.GetTaskQueueUserData`: validates request, resolves
namespace → ID, builds partition RPC name via `tqid`, calls matching
service, returns per-type entry + version |
| `service/frontend/admin_handler_test.go` | Added unit tests |

## How did you test it?

- [x] built
- [x] run locally and tested manually
- [x] added new unit test(s)
- [x] added new integration test(s) - not applicable, not touching
persistence layer
- [x] added new functional test(s)

### Unit tests

100% unit test coverage

  | Test case | Input | Expected |
  |---|---|---|
  | Nil request | `request == nil` | `errRequestNotSet` |
  | Empty namespace | `namespace == ""` | `errNamespaceNotSet` |
| Namespace not found | Namespace registry returns not-found | Error
propagated; matching never called |
| Invalid task queue name | `task_queue` starts with `/_sys/` |
`INVALID_ARGUMENT` from `tqid.NewTaskQueueFamily`; matching never called
|
| Root partition | `partition_id=0`, workflow type | Sends bare name
`my-queue` to matching; returns correct `user_data` and `version` |
| Non-root partition | `partition_id=1`, workflow type | Sends mangled
name `/_sys/my-queue/1` to matching |
| No per-type data | Matching returns response with empty `per_type` map
| `user_data` is nil; `version` still populated |
| Matching error | Matching client returns error | Error propagated to
caller |

### Functional tests

| Test | Setup | What it verifies |

|------------------------------------------------|--------------------------------|----------------------------------------------------------------------------------|
| TestAdminGetTaskQueueUserData_RootPartition | Write fairness weight
config to a workflow task queue | Admin RPC resolves namespace by name,
routes to root partition (partition_id=0), returns version > 0 and
non-nil per-type data |
| TestAdminGetTaskQueueUserData_NonRootPartition | Same write, then poll
until non-root partition replicates | Admin RPC routes to a non-root
partition (partition_id=1) via mangled name, returns the same version as
root after replication |

### Manual tests

<details>
<summary>Setup</summary>

1. Build and start the server: `make temporal-server && make
start-sqlite`
2. Create namespace: `temporal operator namespace create default`
3. Insert assignment rule: `temporal task-queue versioning
insert-assignment-rule`

</details>

<details>
<summary>Case 1 — Root partition, workflow type</summary>

```
grpcurl -plaintext \
  -d '{"namespace":"default","task_queue":"my-queue","task_queue_type":"TASK_QUEUE_TYPE_WORKFLOW"}' \
  localhost:7233 \
  temporal.server.api.adminservice.v1.AdminService/GetTaskQueueUserData
```

```json
{
  "version": "1"
}
```
</details>

<details>
<summary>Case 2 — Root partition, activity type</summary>

```
grpcurl -plaintext \
  -d '{"namespace":"default","task_queue":"my-queue","task_queue_type":"TASK_QUEUE_TYPE_ACTIVITY"}' \
  localhost:7233 \
  temporal.server.api.adminservice.v1.AdminService/GetTaskQueueUserData
```

```json
{
  "version": "1"
}
```
</details>

<details>
<summary>Case 3 — Non-root partition, workflow type</summary>

```
grpcurl -plaintext \
  -d '{"namespace":"default","task_queue":"my-queue","task_queue_type":"TASK_QUEUE_TYPE_WORKFLOW","partition_id":1}' \
  localhost:7233 \
  temporal.server.api.adminservice.v1.AdminService/GetTaskQueueUserData
```

```json
{
  "version": "1"
}
```
</details>

<details>
<summary>Case 4 — Non-root partition, activity type</summary>

```
grpcurl -plaintext \
  -d '{"namespace":"default","task_queue":"my-queue","task_queue_type":"TASK_QUEUE_TYPE_ACTIVITY","partition_id":1}' \
  localhost:7233 \
  temporal.server.api.adminservice.v1.AdminService/GetTaskQueueUserData
```

```json
{
  "version": "1"
}
```

</details>

<details>
<summary>Case 5 — Non-root partition, activity type, with user
data</summary>

Setup: Add rate limit config

```
grpcurl -plaintext \
  -d '{
    "namespace": "default",
    "task_queue": "my-queue",
    "task_queue_type": "TASK_QUEUE_TYPE_ACTIVITY",
    "update_queue_rate_limit": {
      "rate_limit": {
        "requests_per_second": 50.0
      },
      "reason": "manual test"
    }
  }' \
  localhost:7233 \
  temporal.api.workflowservice.v1.WorkflowService/UpdateTaskQueueConfig
```

```
grpcurl -plaintext \
  -d '{
    "namespace": "default",
    "task_queue": "my-queue",
    "task_queue_type": "TASK_QUEUE_TYPE_ACTIVITY"
  }' \
  localhost:7233 \
  temporal.server.api.adminservice.v1.AdminService/GetTaskQueueUserData
```

```json
{
  "userData": {
    "config": {
      "queueRateLimit": {
        "rateLimit": {
          "requestsPerSecond": 50
        },
        "metadata": {
          "reason": "manual test",
          "updateTime": "2026-04-13T21:40:39.888Z"
        }
      }
    }
  },
  "version": "2"
}
```

</details>

<details>
<summary>Case 6 — Namespace not found</summary>

```
grpcurl -plaintext \
  -d '{"namespace":"nonexistent","task_queue":"my-queue","task_queue_type":"TASK_QUEUE_TYPE_WORKFLOW"}' \
  localhost:7233 \
  temporal.server.api.adminservice.v1.AdminService/GetTaskQueueUserData
```

```
ERROR:
  Code: NotFound
  Message: Namespace nonexistent is not found.
```

`NOT_FOUND` from namespace registry; matching never called.

</details>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:35:19 -07:00
..