Use canonical locations for compute placement - #264
Conversation
Resolve the location-native placement change against the location source abstraction and runtime classes that landed on main. - Placements validate by location name through locations.ListPlacementLocations, so both the NetworkServices and Locations sources keep working. A PlacementLocation now carries Ready, true for every LocationBinding and the Ready condition for a Location, and only Ready locations may be placed at. - PropagationPolicies key on (location, runtime class) with names of the form location-<name>[-class-<class>], selecting on topology.datum.net/location. - Location resolution and the ServingLocation watch stay behind the networking integration and the source-selected kind guard from main. - Agent tooling, instance pod identity labels, and the provider fleet view read the location instead of a city code; the mismatch reason is LocationMismatch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Naming locations stays the default. A placement may instead carry a locationSelector, a label selector matched against the topology of the project's Ready locations (city code, region, and any other key the platform projects). Every matching location receives a deployment, and the set follows the fleet: the workload controller watches the project's placement locations, so a location that starts or stops matching is picked up or torn down without polling. - API: placements[].locationSelector, exactly one of locations or locationSelector (CEL and webhook); status.placements[].locations reports what each placement resolved to; NoMatchingLocations names a placement that resolves to nothing. - Admission rejects an empty or malformed selector and one that matches no Ready location, mirroring how an unknown location name is rejected. - CLI: deploy --location-selector, mutually exclusive with --location and --city; describe shows the selector and resolved locations. - Portal: placements show the selector and the locations it resolved to. The controller watches the project's placement locations so a selector follows the fleet. The watch is the first thing to watch LocationBindings, so the ClusterRole now grants it, and it is kept off any control plane that does not serve the kind: an informer whose list is rejected never syncs and blocks every controller on the manager. Test locations repeated across files are named constants so the linter accepts them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
--city no longer resolves city codes to location names on the client. It emits a locationSelector on topology.datum.net/city-code, so the placement deploys to every Ready location in those cities and follows locations as they are added or removed. Admission already rejects a city that matches no Ready location, which replaces the client-side check, and --all-matching-locations has no purpose left. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
--location and --city already completed, but only for the first element: the shell matches candidates against the whole value typed so far, so "--location=us-east-1,eu-" offered nothing. Candidates now carry the typed elements in front of them, already-chosen elements are not repeated, and no space is appended so a comma can follow. deploy offers only Ready locations and their city codes, since admission rejects anything else; list and describe filters keep offering every projected location. --location-selector completes the key=value pairs found in the topology of Ready locations, so the available topology keys can be discovered from the shell. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A location can be Ready for the platform generally yet have no compute cell behind it. The platform records where each service runs as a ServiceAvailability and mirrors those records into entitled projects, so placement now reads them: a location is placeable when it is Ready and a record for the compute service names it and reports Available. Admission, selector resolution, placement status, and shell completion all share that one predicate. A control plane that does not serve the kind enforces no availability gate, so placement there behaves as before the mirror existed. The workload controller watches the mirrored records, per control plane and only where the kind is served, so a placement starts or stops running at a location as compute availability there changes. The ClusterRole grants the watch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
e3e345b to
3a081cf
Compare
Workloads stored before placement moved to locations still name city codes. Pruning the field on rollout would leave their placements resolving to nothing and tear down everything they run, so the field stays, deprecated, and is rewritten into the selector --city now emits: every location whose topology.datum.net/city-code is one of the codes. Admission rewrites it on any write, so nothing new is stored with it. The workload controller rewrites a stored workload the first time it reconciles it after the rollout and writes it back before deriving deployments; until that write succeeds the workload keeps what it runs. A placement that names city codes beside locations or a selector is rejected rather than guessed at. Deployments from that era carry no location, so the federator reads the city off their hub copy while finalizing them and removes the city-keyed PropagationPolicies once no deployment routed by that city remains. The CLI and portal show unmigrated placements by their cities in the meantime. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deploying a workload with an HTTP port now ends with a URL. Previously --port opened a port and gave the developer nothing to point a browser at. --http-port declares the workload an HTTP service and publishes it on a Datum-managed HTTPS URL, backed by a NetworkService and an HTTPProxy that load-balance across every city the workload runs in. --no-http removes it. --port is removed and hard-errors rather than aliasing to --http-port: silently aliasing would publish every existing workload on the next upgrade. Adds `compute open` (heroku/fly/railway convention) and a `compute domains` group for listing domains, showing one workload's URL in detail with per-city backend health, and attaching custom hostnames. Surfaces the URL in `compute workloads` and includes it in `compute destroy`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
deploy calls Declare on every run, so the common case is a workload that already has a URL. Pin that a redeploy neither recreates it nor churns it when nothing changed, that a changed port is written through, and that the canonical hostname survives — reissuing it would silently break every reference a developer has already shared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Advanced proxy configuration — custom hostnames, DNS verification, path routing, certificates — belongs to dedicated ALB tooling, not the compute plugin. This plugin publishes a URL and stops there. Removes `compute domains` and `compute open`. The per-city backend health view moves into `workloads describe`, which already exists and is where a developer debugging their own workload looks; its health row is labelled Serving so it reads distinctly from the workload's own Health line. deploy still reads and carries forward the custom hostnames already on the proxy, and still fails closed if it cannot read them. That matters more now, not less: hostnames are configured out of band, so a redeploy that clobbered them would break something this plugin never saw being set up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stacking on the locations work leaves the URL views speaking the old vocabulary. The per-location backend table said CITY, and its next-steps line told users to run 'compute instances --city=', a flag that no longer exists — the suggested command would have failed. Renames url.Location.City to Location, the column to LOCATION, and the suggested flag to --location, so every place a URL reports where it is serving from uses one word for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI's golangci-lint run fails on this branch: deployFromFlags crossed the gocyclo limit at 32 once the locations validation and the HTTP service handling both landed in it, and two test fixtures wrote "http" where the httpPortName constant already exists. Lifts the three mutually exclusive placement flags into resolveLocationSelector, which is pure and now has the coverage the inline version never could — everything else in deployFromFlags needs a control plane behind the activation gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kevwilliams
left a comment
There was a problem hiding this comment.
Placement move from city codes to canonical locations is well handled end to end: the deprecated cityCodes field is rewritten into an equivalent locationSelector on admission (Default hook) and again by the workload controller on first reconcile after rollout, with writes gated through validation so a workload never loses its running deployments. The federator correctly recognizes deployments with no LocationRef as pre-migration, reads the city off the hub copy before deleting it, and cleans up the old city-keyed PropagationPolicies once nothing references them, while new policies use the location-keyed naming scheme. CLI, webhook, RBAC, CRDs, docs, and e2e fixtures were all updated consistently, and the new code is covered by solid table-driven tests. No missed call sites or migration gaps found.
feat(cli): publish workloads on a URL with --http-port
…compute # Conflicts: # go.mod # go.sum
|
@ecv @kevwilliams had to resolve some merge conflicts with main. Diff is larger because I merged #289 into this branch by accident. |
Main moved placement from city codes to locations (#264), added runtime classes (#259), published a shared instance-type catalog, and grew `datumctl compute deploy` a URL-publishing path (#289). The assistant's workload-creation surface was rebuilt on top of those rather than beside them. Semantic resolutions: - Placements are locations. `workloadspec.Placement` drops `CityCodes` for `Locations []string` plus `LocationSelector`, exactly one of which must be set; render emits `spec.placements[].locations` or the selector and refuses an empty or malformed one, the way admission does. The render tool's schema, its notes, the diff summary, the workload-create skill, llms-full.txt and the README all say "location" and point the model at `compute_locations_list` for the names, with `locationSelector` named as the way to place in every location of a city or region. - Location resolution keeps main's structure. `SourceServiceAvailability` is gone; `locations.ListAvailableLocations` is built on main's `AvailableLocations` and errors with `ErrAvailabilityNotServed` when the control plane serves no availability, so `compute_locations_list` still fails loud rather than reporting a project with nowhere to run. The manager's placement reads, which treat an unserved kind as no gate at all, are unchanged. The tool now reports `placeable` and `ready` alongside the name, city code and topology. - Instance types read `pkg/instancetype`. `validation.SupportedInstanceTypes` and the duplicated sizing table in the agent are deleted, along with TODO(#137): the catalog is the single source now. - Runtime classes: `workloadspec.Input` gains an optional `RuntimeClass`, passed through verbatim with no default, so the server picks its own. - `internal/cmd/compute/deploy/deploy.go` is main's version wholesale, for `--location`, `--location-selector`, `--city` and `--http-port`. The CLI no longer renders through workloadspec, so the parity test that pinned the two together is deleted rather than left stale. - Docs take main's triage-skill changes with this branch's `compute_` renames on top, and workload-create now says that a public HTTPS URL is published separately — today only by `datumctl compute deploy --http-port` — so `ports` makes a port reachable on the instance's address, not on a managed URL. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
Compute placement now speaks the same language as the rest of Datum Cloud. A workload is placed at locations, the names you see in
datumctl get locations, instead of airport-style city codes. There are two ways to say where a placement runs:The CLI, portal, API, samples, and docs all move together so a developer sees one concept from the first
deployto the instance detail page.What a developer sees
Deploy by name. Tab completion offers the Ready locations projected into the project, and keeps working through a comma-separated list.
--citycompletes the city codes those locations serve, and--location-selectorcompletes thekey=valuetopology pairs they carry, so the available topology keys can be discovered from the shell:The interactive prompt asks for locations and "min replicas per location" instead of cities.
Deploy by topology.
--location-selectortakes ordinary label selector syntax. Every Ready location that matches gets a deployment. A location that later starts matching is picked up on its own, and one that stops matching is torn down:In a manifest the same placement is a standard selector block, and the workload's status reports what it resolved to:
A placement sets either
locationsorlocationSelector, never both. An empty selector is rejected rather than treated as "everywhere". A placement that resolves to nothing stays visible in status with aNoMatchingLocationscondition instead of silently disappearing.City codes are a shortcut for a selector.
--city=DFW,IADis shorthand for a location selector ontopology.datum.net/city-code, so the placement runs at every Ready location in those cities and keeps tracking them as locations are added or removed. The saved workload config records the selector, and a city with no Ready location is rejected at admission with the list of locations that do exist. Pin a placement to specific locations with--locationinstead.Inspect with
--location. Every list and describe command filters by location and shows aLOCATIONcolumn, andworkloads describeshows a placement's selector alongside the locations it resolved to:rollout,scale,restart, anddestroyfollow the same pattern.Mistakes are caught at admission. An unknown location, a location that is not Ready or where compute is not available, a duplicate in one placement, or a selector that is empty, malformed, or matches no such location is rejected with a field-level error listing the valid choices, rather than landing in a deployment that never schedules.
Portal (consumer and provider) labels workload and instance views as "Locations", shows the location on instance cards and page chrome, reports replicas per location, and for selector-based placements shows the selector and the locations it resolved to.
API changes
This changes the alpha API, with a shim so workloads stored before the rollout keep running:
Workload.spec.placements[].cityCodesis replaced byplacements[].locations(a list of{name}references) orplacements[].locationSelector(a label selector over location topology). Exactly one must be set, enforced by CEL on the CRD and by the webhook.cityCodesstays as a deprecated field so existing objects survive the CRD change. Admission rewrites a placement that only names city codes into the equivalent city-code selector on every write, and the workload controller rewrites each stored workload the first time it reconciles it after the rollout, writing it back before deriving deployments. Until that write succeeds a workload keeps what it already runs. The federator finalizes pre-location deployments by reading the city off their hub copy and removing the city-keyed propagation policies once nothing routed by that city remains.Workload.status.placements[].locationslists the locations a placement currently resolves to.WorkloadDeployment.spec.cityCodebecomesspec.locationRef. Thestatus.locationfield is removed since the location is now declared in spec, andkubectl get workloaddeploymentsshows aLOCATIONcolumn.compute.datumapis.com/locationlabel instead ofcity-code.topology.datum.net/locationlabel.Placements are validated and resolved through the location source abstraction, so both the NetworkServices and Locations sources work. A location is placeable when it is Ready and compute is available there. Readiness comes from the Location (every LocationBinding counts as Ready; a Location must carry a true Ready condition). Availability comes from the
ServiceAvailabilityrecords the platform mirrors into the project: a record for thecomputeservice naming the location withAvailable=True. A control plane that does not serve that kind enforces no availability gate. The workload controller watches both the project's locations and its availability records, so placements follow changes without polling.Samples (including a new
location-selector-sandbox.yaml), generated API docs, the compute DX enhancement doc, and e2e fixtures are updated to match.Validation
make testtsc --noEmittsc --noEmit