refactor: remove unused v0.3 task discovery runtime
This commit is contained in:
@@ -56,7 +56,6 @@ type Snapshot struct {
|
||||
QuotaRevision int64
|
||||
TenantMaxConcurrentCalls int64
|
||||
QuotaValidUntil time.Time
|
||||
DiscoveryCursor string
|
||||
FetchedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
SIP json.RawMessage
|
||||
@@ -221,49 +220,6 @@ func (c *Client) ReadTaskStatus(ctx context.Context, taskID, tenantID, tenantKey
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadTaskDiscovery reads one page of latest task states after a durable event ID.
|
||||
// SaaS guarantees page completeness and ordering; no per-item event ID is provided.
|
||||
func (c *Client) ReadTaskDiscovery(ctx context.Context, after string) (TaskDiscovery, error) {
|
||||
from, err := parseEventCursor(after)
|
||||
if err != nil {
|
||||
return TaskDiscovery{}, fmt.Errorf("invalid task-discovery after cursor: %w", err)
|
||||
}
|
||||
path := configReadPath + "/tasks?" + url.Values{"after": {after}}.Encode()
|
||||
body, err := c.getTaskDiscovery(ctx, path)
|
||||
if err != nil {
|
||||
return TaskDiscovery{}, err
|
||||
}
|
||||
var response taskDiscoveryResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return TaskDiscovery{}, fmt.Errorf("decode task discovery: %w", err)
|
||||
}
|
||||
if response.DispatcherID != c.dispatcherID {
|
||||
return TaskDiscovery{}, errors.New("task-discovery dispatcher identity does not match the request")
|
||||
}
|
||||
to, err := parseEventCursor(response.NextCursor)
|
||||
if err != nil {
|
||||
return TaskDiscovery{}, fmt.Errorf("invalid task-discovery next cursor: %w", err)
|
||||
}
|
||||
if (len(response.Tasks) == 0 && to != from) || (len(response.Tasks) != 0 && to <= from) {
|
||||
return TaskDiscovery{}, errors.New("task-discovery page cursor does not match returned tasks")
|
||||
}
|
||||
seen := make(map[string]struct{}, len(response.Tasks))
|
||||
tenantByID, idByTenant := make(map[string]string), make(map[string]string)
|
||||
for _, task := range response.Tasks {
|
||||
if _, duplicate := seen[task.TaskID]; duplicate {
|
||||
return TaskDiscovery{}, fmt.Errorf("task discovery contains duplicate task ID %q", task.TaskID)
|
||||
}
|
||||
seen[task.TaskID] = struct{}{}
|
||||
if err := validateTenantBinding(tenantByID, idByTenant, task.TenantID, task.TenantKey); err != nil {
|
||||
return TaskDiscovery{}, err
|
||||
}
|
||||
}
|
||||
return TaskDiscovery{
|
||||
DispatcherID: response.DispatcherID, FromCursor: after, NextCursor: response.NextCursor,
|
||||
Tasks: response.Tasks, Body: append(json.RawMessage(nil), body...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseEventCursor(cursor string) (uint64, error) {
|
||||
value, err := strconv.ParseUint(cursor, 10, 64)
|
||||
if err != nil || strconv.FormatUint(value, 10) != cursor {
|
||||
@@ -298,17 +254,6 @@ func (c *Client) getConfig(ctx context.Context, path string) (json.RawMessage, e
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c *Client) getTaskDiscovery(ctx context.Context, path string) (json.RawMessage, error) {
|
||||
body, err := c.get(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := contract.ValidateLocalTaskDiscovery(body); err != nil {
|
||||
return nil, fmt.Errorf("validate task-discovery response: %w", err)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c *Client) get(ctx context.Context, path string) (json.RawMessage, error) {
|
||||
relative, err := url.Parse(path)
|
||||
if err != nil {
|
||||
@@ -373,12 +318,6 @@ type sipConfigResponse struct {
|
||||
} `json:"artifact"`
|
||||
}
|
||||
|
||||
type taskDiscoveryResponse struct {
|
||||
DispatcherID string `json:"dispatcher_id"`
|
||||
NextCursor string `json:"next_cursor"`
|
||||
Tasks []DiscoveredTask `json:"tasks"`
|
||||
}
|
||||
|
||||
type taskConfigResponse struct {
|
||||
Resource string `json:"resource"`
|
||||
DispatcherID string `json:"dispatcher_id"`
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
package configread
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const eventTestDispatcherID = "11111111-1111-4111-8111-111111111111"
|
||||
|
||||
func eventTestClient(t *testing.T, server *httptest.Server) *Client {
|
||||
t.Helper()
|
||||
client, err := NewClient(server.URL, eventTestDispatcherID, mockTestSecret, server.Client())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func TestEventCursorPagesUseOneTaskListAndAllowRepeatedTaskIDs(t *testing.T) {
|
||||
pages := map[string]string{
|
||||
"0": "task-discovery-page-v0.3.json",
|
||||
"2": "task-discovery-updated-v0.3.json",
|
||||
"3": "task-discovery-removed-v0.3.json",
|
||||
"4": "task-discovery-empty-v0.3.json",
|
||||
}
|
||||
var got []string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
got = append(got, r.URL.RequestURI())
|
||||
if r.Header.Get(dispatcherIDHeader) != eventTestDispatcherID || r.Header.Get(dispatcherSecretHeader) != mockTestSecret {
|
||||
t.Error("Dispatcher credentials were not sent")
|
||||
}
|
||||
name := pages[r.URL.Query().Get("after")]
|
||||
if name == "" || r.URL.Query().Has("page_token") {
|
||||
t.Errorf("unexpected task request: %s", r.URL.RequestURI())
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(readConfigFixture(t, name))
|
||||
}))
|
||||
defer server.Close()
|
||||
client := eventTestClient(t, server)
|
||||
cursor := "0"
|
||||
var statuses []string
|
||||
for range 4 {
|
||||
page, err := client.ReadTaskDiscovery(context.Background(), cursor)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, task := range page.Tasks {
|
||||
statuses = append(statuses, task.TaskID+":"+task.Status)
|
||||
}
|
||||
cursor = page.NextCursor
|
||||
}
|
||||
if cursor != "4" || !reflect.DeepEqual(statuses, []string{"a01:running", "a02:paused", "a01:paused", "a02:removed"}) {
|
||||
t.Fatalf("statuses=%v cursor=%q", statuses, cursor)
|
||||
}
|
||||
want := []string{
|
||||
"/internal/v1/dispatcher/tasks?after=0",
|
||||
"/internal/v1/dispatcher/tasks?after=2",
|
||||
"/internal/v1/dispatcher/tasks?after=3",
|
||||
"/internal/v1/dispatcher/tasks?after=4",
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("queries=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventCursorRejectsBrokenPagesWithoutFallback(t *testing.T) {
|
||||
valid := string(readConfigFixture(t, "task-discovery-page-v0.3.json"))
|
||||
var oversized map[string]any
|
||||
if err := json.Unmarshal([]byte(valid), &oversized); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows := make([]any, 257)
|
||||
for i := range rows {
|
||||
rows[i] = oversized["tasks"].([]any)[0]
|
||||
}
|
||||
oversized["tasks"] = rows
|
||||
oversizedBody, err := json.Marshal(oversized)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cases := map[string]string{
|
||||
"nonprogress": strings.Replace(valid, `"next_cursor": "2"`, `"next_cursor": "0"`, 1),
|
||||
"duplicate": strings.Replace(valid, `"task_id": "a02"`, `"task_id": "a01"`, 1),
|
||||
"wrong dispatcher": strings.Replace(valid, eventTestDispatcherID, "44444444-4444-4444-8444-444444444444", 1),
|
||||
"oversized page": string(oversizedBody),
|
||||
"changes": string(readConfigFixture(t, "task-discovery-invalid-changes-v0.3.json")),
|
||||
"cursor format": string(readConfigFixture(t, "task-discovery-invalid-cursor-v0.3.json")),
|
||||
"old contract": string(readConfigFixture(t, "task-discovery-snapshot-v0.2.json")),
|
||||
}
|
||||
for name, body := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
requests := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(body))
|
||||
}))
|
||||
defer server.Close()
|
||||
client := eventTestClient(t, server)
|
||||
if _, err := client.ReadTaskDiscovery(context.Background(), "0"); err == nil {
|
||||
t.Fatal("invalid task page was accepted")
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("requests=%d, want one without fallback", requests)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventCursorRejectsExpiredStatusWithoutReset(t *testing.T) {
|
||||
requests := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusGone)
|
||||
_, _ = w.Write([]byte(`{"schema_version":"task-discovery.v0.3-proposal","resource":"error","error":{"code":"cursor_expired","message":"expired"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
client := eventTestClient(t, server)
|
||||
if _, err := client.ReadTaskDiscovery(context.Background(), "1"); err == nil {
|
||||
t.Fatal("HTTP 410 accepted")
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("requests=%d, want one without reset to initial cursor", requests)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,17 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const eventTestDispatcherID = "11111111-1111-4111-8111-111111111111"
|
||||
|
||||
func eventTestClient(t *testing.T, server *httptest.Server) *Client {
|
||||
t.Helper()
|
||||
client, err := NewClient(server.URL, eventTestDispatcherID, mockTestSecret, server.Client())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func TestV04SnapshotAndLiveChangesUseSeparateRequests(t *testing.T) {
|
||||
var paths []string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user