Skip to content

Describe a placement group.

GET
/v1/projects/{projectId}/compute/placement-groups/{pgId}
Code sample: Shell / cURL
curl --request GET \
--url https://stratum.example.com/v1/projects/example/compute/placement-groups/example \
--header 'Authorization: Bearer <token>'
projectId
required
string
/^p-[a-z0-9-]+$/
pgId
required
string
/^pg-[a-z0-9-]+$/

The placement group.

Media typeapplication/json

A tenant placement group: a RULE, not a set of nodes.

It carries a strategy, a level and a satisfaction mode, and nothing about specific hosts. A tenant never names a host — they express intent and the scheduler resolves it. Exposing node identity would leak the operator’s physical layout across tenants and would invalidate every group the moment a node was rebuilt.

object
id
required
string
/^pg-[a-z0-9-]+$/
projectId
required
string
/^p-[a-z0-9-]+$/
name
required

Customer-chosen name, unique within the project. Instances declare membership by this name at launch.

string
displayName
string
strategy
required

What the group asks of its members’ placement.

  • spread — keep members apart. The case with real demand, and the one that solves the two-node-database-pair problem.
  • cluster — keep members together. Specified so the resource shape accommodates affinity without needing a second resource later, but not implemented yet: creating a group with this strategy is refused with PLACEMENT_STRATEGY_NOT_AVAILABLE. A group that promised affinity and delivered nothing would leave a tenant believing they were protected, which is precisely what this feature exists to prevent.
string
default: spread
Allowed values: spread cluster
level
required

The topology level the strategy applies at, ordered by blast radius host < rack < az.

A host is one hypervisor node, a rack is a shared PDU and top-of-rack switch, an AZ is a physically separate datacenter.

az is in this enum but is REFUSED at create with PLACEMENT_RULE_NOT_AVAILABLE. A launch is pinned to a single availability zone, so a spread across zones could never place a second member anywhere the first is not — accepting it would promise the strongest separation in the vocabulary and enforce nothing. It is kept in the schema because the value is legal storage-side and unlocking it is a code change rather than a migration. It becomes available when a launch can span zones.

When the cluster cannot differentiate the level it degrades to host every node in one rack, or no node declaring one. A rack spread on a single-rack cluster therefore behaves exactly as a host spread, with no error, rather than refusing every launch after the first. That is a correct description of a one-rack cluster, not a limitation, and the group’s guarantee says when it happened.

Where the distinction does exist, the level is enforced and the ceiling is the number of racks rather than hosts: a member can be refused while a host is still free.

string
default: host
Allowed values: host rack az
satisfaction
required

What happens when the placement cannot satisfy the rule.

  • strict — the launch FAILS, naming the group, the level and the limit. The default.
  • best-effort — place anyway, and report the group as violated in its own violation state. The violation is state on the group rather than only a launch-time warning, because a warning that scrolls past leaves best-effort indistinguishable from no group at all.

strict is the default because a silently-degraded HA pair is the exact failure this feature exists to prevent. A tenant who asked for two instances on separate hosts, got two on one host, and has a warning they did not read is worse off than before they asked: they now believe they are protected.

string
default: strict
Allowed values: strict best-effort
tags

AWS-shape resource tags. Freeform UTF-8 keys (1-128 bytes, no leading/trailing whitespace) mapped to freeform UTF-8 values (0-256 bytes). Max 50 tags per resource. Reserved prefixes (stratum.dev/, stratum.platform_health., stratum:, aws:) return 400 RESERVED_NAMESPACE on customer input. PATCH replaces the whole map..

object
<= 50 properties
key
additional properties
string
<= 256 characters
createdAt
required
string format: date-time
createdBy
string
guarantee

What this group’s spread level protects against, and what it leaves exposed, for the cluster as currently declared.

Computed on read, never stored. It is a function of the level and of what the operator has declared, and the second half moves: declaring topology.power tomorrow changes what a group created today does not promise. A stored answer would be a guarantee frozen at create time.

It exists because silence reads as a guarantee. Two racks on one UPS are not independent, so rack-level spread does not by itself mean power-independent — and an operator or tenant who assumes otherwise has bought a guarantee nobody sold them.

Absent when the control plane could not determine what the cluster declares. An absent statement is honest about not knowing; a guessed one would be the unearned guarantee this field exists to prevent.

object
level
required

The topology level the strategy applies at, ordered by blast radius host < rack < az.

A host is one hypervisor node, a rack is a shared PDU and top-of-rack switch, an AZ is a physically separate datacenter.

az is in this enum but is REFUSED at create with PLACEMENT_RULE_NOT_AVAILABLE. A launch is pinned to a single availability zone, so a spread across zones could never place a second member anywhere the first is not — accepting it would promise the strongest separation in the vocabulary and enforce nothing. It is kept in the schema because the value is legal storage-side and unlocking it is a code change rather than a migration. It becomes available when a launch can span zones.

When the cluster cannot differentiate the level it degrades to host every node in one rack, or no node declaring one. A rack spread on a single-rack cluster therefore behaves exactly as a host spread, with no error, rather than refusing every launch after the first. That is a correct description of a one-rack cluster, not a limitation, and the group’s guarantee says when it happened.

Where the distinction does exist, the level is enforced and the ceiling is the number of racks rather than hosts: a member can be refused while a host is still free.

string
default: host
Allowed values: host rack az
protects
required

What a spread at this level does defend against.

Array<string>
doesNotProtect
required

What it leaves exposed. Never empty for a level this install can express — an empty list would read as “protects against everything”.

Each entry says whether the distinction is expressible on this cluster at all, so a tenant knows whether they could ask for it. It never reports how many racks or power feeds exist, or what they are called: a tenant does not see the operator’s physical layout.

Array<string>
violation

Whether the group’s rule currently holds.

Computed on read from the members’ actual placements, never stored. A stored flag would need every path that moves or removes an instance terminate, migrate, evacuate, the reaper, a group delete — to remember to update it, and the first one that forgot would leave a group claiming a violation that had cleared, or claiming compliance it had lost.

It is also why a violation clears with no operator action: the replacement instance is placed by the rule, and the next read simply recomputes.

Reported for strict groups too, not only best-effort. A strict group is violated when it is created around instances that already break it, or when the cluster shrinks below its member count — strict is a placement-time guarantee, not a maintained invariant, and nothing is ever moved to correct a violation.

Absent when the control plane could not read the members. An unknown answer must not render as violated: false.

object
violated
required
boolean
detail
required

A sentence stating the position either way. Populated even when the group is compliant: a field that appears only on failure trains a reader to treat its absence as proof, and absence is also what a bug looks like.

Names at most eight instances per collocation and then says how many more, so the prose does not grow with the project’s instance quota. collocations is complete and is what a client should act on.

string
collocations

Present only when violated. Each entry is a set of the group’s members sharing one placement value.

It names the instances, never the shared value. At host level the value is a node id the tenant can already read off their own instances; at rack or AZ level it would be the operator’s physical layout, which a tenant never sees.

Array<object>
object
instanceIds
required
Array<string>

Example

{
"strategy": "spread",
"level": "host",
"satisfaction": "strict",
"guarantee": {
"level": "host"
}
}

Resource not found.

Media typeapplication/problem+json
object
type
required
string format: uri
title
required
string
status
required
integer
>= 400 <= 599
detail
string
instance
string format: uri-reference
requestId
required
string
code
required
string
issues

The findings of a rejected IAM policy document (custom and resource policies).

Array<object>

One finding of a rejected IAM policy document. A rejection lists every finding, so the author fixes the document in one round trip.

object
code
required

The finding, e.g. INVALID_DOCUMENT, UNKNOWN_ACTION, ACTION_NOT_APPLICABLE.

string
statement
required

Index of the statement at fault; -1 for the document as a whole.

integer
>= -1
detail
required
string
action

The offending action, where one is at fault (ACTION_NOT_APPLICABLE).

string
errors

Field errors, for codes that carry them: each problem at its field (see FieldError). Documented on the responses that set it, e.g. approveNodeJoin’s INVALID_INPUT and JOIN_APPROVAL_REFUSED. Absent otherwise.

Array<object>

One field’s problem, the stage-2 installer API’s shape. field is the field’s path, message what is wrong with it.

object
field
required

The field’s path, e.g. links[0].interface, disks[1].confirm, mgmt.address or role; empty for the whole request.

string
message
required
string
details

Structured facts about the refusal, for codes that carry them. The shape is fixed per code and documented on the response that answers it, e.g. IMAGE_POOL_MISMATCH (see ImagePoolMismatchDetails) and IMAGE_IN_USE. Absent for codes that carry none.

object
key
additional properties
any

Example

{
"issues": [
{
"code": "ACTION_NOT_APPLICABLE",
"action": "compute:CreateVpc"
}
]
}