diff --git a/backend/controller/dal/dal.go b/backend/controller/dal/dal.go index 506f8fee05..8182d1b833 100644 --- a/backend/controller/dal/dal.go +++ b/backend/controller/dal/dal.go @@ -92,13 +92,8 @@ func runnerFromDB(row sql.GetRunnerRow) Runner { return Runner{} } - key, err := model.ParseRunnerDBKey(string(row.RunnerKey)) - if err != nil { - return Runner{} - } - return Runner{ - Key: key, + Key: row.RunnerKey, Endpoint: row.Endpoint, State: RunnerState(row.State), Deployment: deployment, @@ -315,13 +310,8 @@ func (d *DAL) GetStatus( return Runner{}, fmt.Errorf("invalid attributes JSON for runner %s: %w", in.RunnerKey, err) } - key, err := model.ParseRunnerDBKey(string(in.RunnerKey)) - if err != nil { - return Runner{}, fmt.Errorf("invalid id for runner %s: %w", in.RunnerKey, err) - } - return Runner{ - Key: key, + Key: in.RunnerKey, Endpoint: in.Endpoint, State: RunnerState(in.State), Deployment: deployment, @@ -345,13 +335,9 @@ func (d *DAL) GetStatus( } }), Routes: slices.Map(routes, func(row sql.GetRoutingTableRow) Route { - key, err := model.ParseRunnerDBKey(string(row.RunnerKey)) - if err != nil { - return Route{} - } return Route{ Module: row.ModuleName.MustGet(), - Runner: key, + Runner: row.RunnerKey, Deployment: row.DeploymentName, Endpoint: row.Endpoint, } @@ -371,13 +357,8 @@ func (d *DAL) GetRunnersForDeployment(ctx context.Context, deployment model.Depl return nil, fmt.Errorf("invalid attributes JSON for runner %d: %w", row.ID, err) } - key, err := model.ParseRunnerDBKey(string(row.Key)) - if err != nil { - return nil, fmt.Errorf("invalid id for runner %d: %w", row.ID, err) - } - runners = append(runners, Runner{ - Key: key, + Key: row.Key, Endpoint: row.Endpoint, State: RunnerState(row.State), Deployment: optional.Some(deployment), @@ -596,17 +577,12 @@ func (d *DAL) ReserveRunnerForDeployment(ctx context.Context, deployment model.D cancel() return nil, fmt.Errorf("failed to JSON decode labels for runner %d: %w", runner.ID, err) } - key, err := model.ParseRunnerDBKey(string(runner.Key)) - if err != nil { - cancel() - return nil, fmt.Errorf("invalid id for runner %d: %w", runner.ID, err) - } return &postgresClaim{ cancel: cancel, tx: tx, runner: Runner{ - Key: key, + Key: runner.Key, Endpoint: runner.Endpoint, State: RunnerState(runner.State), Deployment: optional.Some(deployment), @@ -794,12 +770,9 @@ func (d *DAL) GetProcessList(ctx context.Context) ([]Process, error) { if err := json.Unmarshal(row.RunnerLabels, &labels); err != nil { return Process{}, fmt.Errorf("invalid labels JSON for runner %s: %w", row.RunnerKey, err) } - key, err := model.ParseRunnerDBKey(string(row.RunnerKey.MustGet())) - if err != nil { - return Process{}, fmt.Errorf("invalid runner key %s: %w", row.RunnerKey, err) - } + runner = optional.Some(ProcessRunner{ - Key: key, + Key: row.RunnerKey.MustGet(), Endpoint: endpoint, Labels: labels, }) @@ -844,12 +817,9 @@ func (d *DAL) GetIdleRunners(ctx context.Context, limit int, labels model.Labels if err != nil { return Runner{}, fmt.Errorf("%s: %w", "could not unmarshal labels", err) } - key, err := model.ParseRunnerDBKey(string(row.Key)) - if err != nil { - return Runner{}, fmt.Errorf("%s: %w", "invalid runner key", err) - } + return Runner{ - Key: key, + Key: row.Key, Endpoint: row.Endpoint, State: RunnerState(row.State), Labels: labels, @@ -871,17 +841,14 @@ func (d *DAL) GetRoutingTable(ctx context.Context, modules []string) (map[string } out := make(map[string][]Route, len(routes)) for _, route := range routes { - if runnerKey, err := model.ParseRunnerDBKey(string(route.RunnerKey)); err == nil { - - // This is guaranteed to be non-nil by the query, but sqlc doesn't quite understand that. - moduleName := route.ModuleName.MustGet() - out[moduleName] = append(out[moduleName], Route{ - Module: moduleName, - Deployment: route.DeploymentName, - Runner: runnerKey, - Endpoint: route.Endpoint, - }) - } + // This is guaranteed to be non-nil by the query, but sqlc doesn't quite understand that. + moduleName := route.ModuleName.MustGet() + out[moduleName] = append(out[moduleName], Route{ + Module: moduleName, + Deployment: route.DeploymentName, + Runner: route.RunnerKey, + Endpoint: route.Endpoint, + }) } return out, nil } @@ -965,12 +932,8 @@ func (d *DAL) GetIngressRoutes(ctx context.Context, method string) ([]IngressRou return nil, ErrNotFound } return slices.Map(routes, func(row sql.GetIngressRoutesRow) IngressRoute { - key, err := model.ParseRunnerDBKey(string(row.RunnerKey)) - if err != nil { - return IngressRoute{} - } return IngressRoute{ - Runner: key, + Runner: row.RunnerKey, Deployment: row.DeploymentName, Endpoint: row.Endpoint, Path: row.Path, diff --git a/backend/controller/sql/models.go b/backend/controller/sql/models.go index a6b87dc14b..b9ab06e1de 100644 --- a/backend/controller/sql/models.go +++ b/backend/controller/sql/models.go @@ -258,7 +258,7 @@ type Request struct { type Runner struct { ID int64 - Key string + Key model.RunnerKey Created time.Time LastSeen time.Time ReservationTimeout NullTime diff --git a/backend/controller/sql/queries.sql.go b/backend/controller/sql/queries.sql.go index 46001be801..c094646ad9 100644 --- a/backend/controller/sql/queries.sql.go +++ b/backend/controller/sql/queries.sql.go @@ -225,7 +225,7 @@ ORDER BY r.key ` type GetActiveRunnersRow struct { - RunnerKey string + RunnerKey model.RunnerKey Endpoint string State RunnerState Labels []byte @@ -680,7 +680,7 @@ WHERE r.state = 'assigned' ` type GetIngressRoutesRow struct { - RunnerKey string + RunnerKey model.RunnerKey DeploymentName model.DeploymentName Endpoint string Path string @@ -759,7 +759,7 @@ type GetProcessListRow struct { MinReplicas int32 DeploymentName model.DeploymentName DeploymentLabels []byte - RunnerKey optional.Option[string] + RunnerKey NullRunnerKey Endpoint optional.Option[string] RunnerLabels []byte } @@ -800,7 +800,7 @@ WHERE r.key = $1 type GetRouteForRunnerRow struct { Endpoint string - RunnerKey string + RunnerKey model.RunnerKey ModuleName optional.Option[string] DeploymentName model.DeploymentName State RunnerState @@ -831,7 +831,7 @@ WHERE state = 'assigned' type GetRoutingTableRow struct { Endpoint string - RunnerKey string + RunnerKey model.RunnerKey ModuleName optional.Option[string] DeploymentName model.DeploymentName } @@ -877,7 +877,7 @@ WHERE r.key = $1 ` type GetRunnerRow struct { - RunnerKey string + RunnerKey model.RunnerKey Endpoint string State RunnerState Labels []byte @@ -924,7 +924,7 @@ WHERE state = 'assigned' type GetRunnersForDeploymentRow struct { ID int64 - Key string + Key model.RunnerKey Created time.Time LastSeen time.Time ReservationTimeout NullTime diff --git a/backend/controller/sql/schema/001_init.sql b/backend/controller/sql/schema/001_init.sql index ff46b02194..695c39cd4f 100644 --- a/backend/controller/sql/schema/001_init.sql +++ b/backend/controller/sql/schema/001_init.sql @@ -37,6 +37,9 @@ CREATE TABLE modules -- Proto-encoded module schema. CREATE DOMAIN module_schema_pb AS BYTEA; +CREATE DOMAIN runner_key AS varchar; +CREATE DOMAIN controller_key AS varchar; + CREATE TABLE deployments ( id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, @@ -101,7 +104,7 @@ CREATE TABLE runners ( id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- Unique identifier for this runner, generated at startup. - key varchar UNIQUE NOT NULL, + key runner_key UNIQUE NOT NULL, created TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), last_seen TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), -- If the runner is reserved, this is the time at which the reservation expires. @@ -209,7 +212,7 @@ CREATE TYPE controller_state AS ENUM ( CREATE TABLE controller ( id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - key varchar UNIQUE NOT NULL, + key controller_key UNIQUE NOT NULL, created TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), last_seen TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), state controller_state NOT NULL DEFAULT 'live', diff --git a/backend/controller/sql/types.go b/backend/controller/sql/types.go index affab2b23f..20756d683b 100644 --- a/backend/controller/sql/types.go +++ b/backend/controller/sql/types.go @@ -1,10 +1,17 @@ package sql import ( + "database/sql" + "database/sql/driver" "time" + "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" ) type NullTime = optional.Option[time.Time] type NullDuration = optional.Option[time.Duration] +type NullRunnerKey = optional.Option[model.RunnerKey] + +var _ sql.Scanner = (*NullRunnerKey)(nil) +var _ driver.Valuer = (*NullRunnerKey)(nil) diff --git a/internal/model/keys.go b/internal/model/keys.go index de28c44d98..d0cbbd970c 100644 --- a/internal/model/keys.go +++ b/internal/model/keys.go @@ -2,20 +2,25 @@ package model import ( + "crypto/rand" "database/sql" "database/sql/driver" "fmt" - "math/rand" + "math/big" "reflect" "strconv" "strings" ) func NewRunnerKey(hostname string, port string) RunnerKey { + suffix, err := rand.Int(rand.Reader, big.NewInt(10000)) + if err != nil { + panic(err) + } return keyType[runnerKey]{ Hostname: hostname, Port: port, - Suffix: rand.Intn(10000), + Suffix: int(suffix.Int64()), } } func NewLocalRunnerKey(suffix int) RunnerKey { @@ -23,23 +28,23 @@ func NewLocalRunnerKey(suffix int) RunnerKey { Suffix: suffix, } } -func ParseRunnerKey(key string) (RunnerKey, error) { return parseKey[RunnerKey](key, true) } -func ParseRunnerDBKey(key string) (RunnerKey, error) { return parseKey[RunnerKey](key, false) } +func ParseRunnerKey(key string) (RunnerKey, error) { return parseKey[RunnerKey](key, true) } type runnerKey struct{} type RunnerKey = keyType[runnerKey] func NewControllerKey(hostname string, port string) ControllerKey { + suffix, err := rand.Int(rand.Reader, big.NewInt(10000)) + if err != nil { + panic(err) + } return keyType[controllerKey]{ Hostname: hostname, Port: port, - Suffix: rand.Intn(10000), + Suffix: int(suffix.Int64()), } } func ParseControllerKey(key string) (ControllerKey, error) { return parseKey[ControllerKey](key, true) } -func ParseControllerDBKey(key string) (ControllerKey, error) { - return parseKey[ControllerKey](key, false) -} type controllerKey struct{} type ControllerKey = keyType[controllerKey] @@ -142,7 +147,6 @@ func (d keyType[T]) string(includeKind bool) string { func (d keyType[T]) MarshalText() ([]byte, error) { return []byte(d.String()), nil } func (d *keyType[T]) UnmarshalText(bytes []byte) error { - fmt.Printf("marshal text: %s\n", string(bytes)) id, err := parseKey[keyType[T]](string(bytes), true) if err != nil { return err diff --git a/internal/model/keys_test.go b/internal/model/keys_test.go index dd49f45cad..8ff8092bb0 100644 --- a/internal/model/keys_test.go +++ b/internal/model/keys_test.go @@ -49,7 +49,7 @@ func TestRunnerKey(t *testing.T) { assert.NoError(t, err) assert.Equal(t, test.key, parsed, "expected %v for %v after parsing", test.key, parsed) - parsed, err = ParseRunnerDBKey(value) + parsed, err = parseKey[RunnerKey](value, false) assert.NoError(t, err) assert.Equal(t, test.key, parsed, "expected %v for %v after parsing db key", test.key, parsed) } diff --git a/sqlc.yaml b/sqlc.yaml index 05e2911a72..3211426d7b 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -30,6 +30,14 @@ sql: - db_type: "pg_catalog.varchar" nullable: true go_type: "github.com/alecthomas/types/optional.Option[string]" + - db_type: "runner_key" + go_type: "github.com/TBD54566975/ftl/internal/model.RunnerKey" + - db_type: "runner_key" + nullable: true + go_type: + type: "NullRunnerKey" + - db_type: "controller_key" + go_type: "github.com/TBD54566975/ftl/internal/model.ControllerKey" - db_type: "text" go_type: "string" - db_type: "text"