better/compute
API

networks

Networks are a tenant's private networks (VPCs) on a provider account, with their subnets and firewall rules ([firewallRules](/docs/reference/api/firewall-rules)). A network belongs to one provider pool, so to that pool's account, and the provider must support tenant networks (Google Cloud does; a provider that does not is `UNSUPPORTED`). The calls record what the tenant wants and return at once. The `vps.networks` reconciler then converges the provider: it creates the VPC, then the subnets and the firewall rules, then tags the network's instances, and removes what the tenant deleted, rules first, then subnets, then the network. `state` shows where it is: `pending`, `ready`, `deleting`, `deleted` or `error` (with a `message`). The network's `state` covers the VPC and its subnets (`ready` when all are; `error` with the failing resource in `message`); each subnet and each rule also has its own `state`, and a rule the provider refuses is `error` on the rule without taking the network out of `ready`. The reconciler publishes `network.ready`, `network.error`, `network.subnet.ready`, `network.rule.ready`, `network.rule.error` and `network.deleted`. An `error` is retried by the periodic pass, which runs every five minutes and also re-applies what drifted at the provider (a firewall deleted by hand).

Networks are a tenant's private networks (VPCs) on a provider account, with their subnets and firewall rules (firewallRules). A network belongs to one provider pool, so to that pool's account, and the provider must support tenant networks (Google Cloud does; a provider that does not is UNSUPPORTED). The calls record what the tenant wants and return at once. The vps.networks reconciler then converges the provider: it creates the VPC, then the subnets and the firewall rules, then tags the network's instances, and removes what the tenant deleted, rules first, then subnets, then the network. state shows where it is: pending, ready, deleting, deleted or error (with a message). The network's state covers the VPC and its subnets (ready when all are; error with the failing resource in message); each subnet and each rule also has its own state, and a rule the provider refuses is error on the rule without taking the network out of ready. The reconciler publishes network.ready, network.error, network.subnet.ready, network.rule.ready, network.rule.error and network.deleted. An error is retried by the periodic pass, which runs every five minutes and also re-applies what drifted at the provider (a firewall deleted by hand).

Only instances that own their machine are tagged: not deleting, lost or failed, and an attached one once it is claimed and while it has no OwnershipLost, AttachConflict or DuplicateMachine condition; the provider is told the instance id and leaves a machine labelled for another instance alone. Tags are set after the rules, and a machine whose tags were set within the last hour is skipped, so a network with more machines than one run can tag gets its rules at once and its tags over the next runs; a tag removed by hand comes back within the hour.

The provider-side name of a network, a subnet and a rule is bc- and the resource's own id, lower-cased. A machine on the network carries the tag bc- and 16 hex digits of the sha256 of its instance id, so a rule can target single instances.

Every tenant's networks on an account live in that account's one provider project, so they share its quotas (VPC networks, subnetworks and firewall rules per project): one tenant's networks use up what the others on the account can get. To bound that, a tenant has at most 10 networks that are not deleted, and a network at most 16 subnets and 100 firewall rules (QUOTA_EXCEEDED, with the cap in details.limit). The project's own quotas still apply on top, and a refusal of the provider shows as error with its message.

All 11 methods are served by @better-compute/server 0.3.0.

MethodKindAuthorizationPhaseStatus
createmutationcompute:networks:create @ compute-networkv1.0registered
listreadcompute:networks:read @ compute-tenant + itemsv1.0registered
getreadcompute:networks:read @ compute-networkv1.0registered
updatemutationcompute:networks:update @ compute-networkv1.0registered
deletemutationcompute:networks:delete @ compute-networkv1.0registered
addSubnetmutationcompute:networks:update @ compute-networkv1.0registered
removeSubnetmutationcompute:networks:update @ compute-networkv1.0registered
adoptmutationcompute:providers:manage @ compute-fleetv1.0registered
natGatewaymutationcompute:networks:update @ compute-networkv1.0registered
peermutationcompute:networks:update @ compute-networkv1.0registered
routesmutationcompute:networks:update @ compute-networkv1.0registered

create

POST /api/compute/networks/create

Records a network on the pool's provider account and returns it pending, with no subnets yet.

  • The pool must be offered to the tenant, a provider pool, and its account's provider must support networks; otherwise the call is UNSUPPORTED (NOT_FOUND for a pool the tenant cannot see).
  • name is 1 to 42 characters, [a-z]([-a-z0-9]*[a-z0-9])?, unique among the tenant's networks that are not deleted (CONFLICT otherwise; the name of a deleted network is free again).
  • A tenant that already has 10 networks that are not deleted (one being deleted still counts) is QUOTA_EXCEEDED.
  • Decided as compute:networks:create on the network it would create (compute-network, under the tenant), whose attributes are providerAccountId, poolId and projectId. A refusal leaves nothing behind.
  • On a pool whose billing mode is metered, the tenant's budgets are checked after the decision, as for instances.create: when the pool asks for it or the tenant's offer is stop-new, drain or suspend, a spent budget is SPEND_LIMIT_REACHED. A root caller creating a network for a tenant is checked against that tenant's budgets. A suspended offer or a region its allowedRegions exclude refuses the create on any pool.

The network id derives from the tenant, the caller and idempotencyKey. A retry with the same key returns the same network; the same key with another pool, name, project or description is CONFLICT. The caller owns the network. Publishes network.created.

  • Kind: mutation
  • Authorization: compute:networks:create @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
projectIdstringat most 56 characters; matches ^prj_[0-9a-hjkmnp-tv-z]{26,52}$
poolIdstringyesat most 57 characters; matches ^pool_[0-9a-hjkmnp-tv-z]{26,52}$
namestringyesat most 42 characters; matches ^[a-z]([-a-z0-9]{0,40}[a-z0-9])?$
descriptionstringat most 256 characters
cidrstring
idempotencyKeystringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$

list

POST /api/compute/networks/list

Returns the tenant's networks with their subnets, oldest first; poolId and projectId narrow it, and includeDeleted: true adds deleted ones. At most 500 are returned. Decided as compute:networks:read on the tenant.

  • Kind: read
  • Authorization: compute:networks:read @ compute-tenant + items
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
poolIdstringat most 57 characters; matches ^pool_[0-9a-hjkmnp-tv-z]{26,52}$
projectIdstringat most 56 characters; matches ^prj_[0-9a-hjkmnp-tv-z]{26,52}$
includeDeletedbooleandefault false

get

POST /api/compute/networks/get

Returns one network with its subnets (pending, ready, deleting, deleted or error). A network of another tenant is NOT_FOUND here, and is refused by the decision (ACCESS_DENIED) before that. Decided as compute:networks:read on the network.

  • Kind: read
  • Authorization: compute:networks:read @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$

update

POST /api/compute/networks/update

Changes the network's description. The name, pool and account never change, because the provider cannot rename a network. A network being deleted is INVALID_STATE. Decided as compute:networks:update on the network.

  • Kind: mutation
  • Authorization: compute:networks:update @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
descriptionstringyesat most 256 characters

delete

POST /api/compute/networks/delete

Marks the network, its subnets and its rules for deletion and returns it deleting, along with its routes, peerings (also those another network holds with it) and NAT gateways. The reconciler deletes the routes, peerings and gateways, then the rules, then the subnets, then the network, and then the network is deleted and publishes network.deleted.

  • INVALID_STATE while instances that are not deleted are on the network, with their ids in details.instanceIds: delete or move them first. Likewise while load balancers are on one of its subnets (details.loadBalancerIds).
  • INVALID_STATE while filesystems that are not deleted are on the network, with their ids in details.filesystemIds: delete them first.
  • INVALID_STATE while shared provider volumes that are not deleted are reached over the network, with their ids in details.volumeIds: delete them first.
  • Deleting a network that is being deleted returns it unchanged.

Publishes network.deleting. Decided as compute:networks:delete on the network.

  • Kind: mutation
  • Authorization: compute:networks:delete @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$

addSubnet

POST /api/compute/networks/addSubnet

Adds a subnet and returns the network with it pending.

  • cidr is an IPv4 range inside 10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16 (RFC 1918), with a prefix from /8 to /29, written as its network address (10.0.0.0/20). Anything else is INVALID_INPUT.
  • It must not overlap another subnet of the network, in any region: that is CONFLICT, and the database refuses it too.
  • region must be a region of the zones the account is configured with (us-central1 for the zone us-central1-a); otherwise INVALID_INPUT, with the allowed regions in details.
  • The name is unique in the network; the name of a deleted subnet is free again.
  • A network that already has 16 subnets (not counting removed ones) is QUOTA_EXCEEDED.
  • A network being deleted is INVALID_STATE.

Publishes network.subnet.ready once the provider has the subnet. Decided as compute:networks:update on the network.

  • Kind: mutation
  • Authorization: compute:networks:update @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
namestringyesat most 42 characters; matches ^[a-z]([-a-z0-9]{0,40}[a-z0-9])?$
regionstringyesmatches ^[a-z][a-z0-9-]{1,38}[a-z0-9]$
zonestringmatches ^[a-z][a-z0-9-]{1,62}[a-z0-9]$
cidrstringyes
privateAccessboolean

removeSubnet

POST /api/compute/networks/removeSubnet

Marks a subnet for deletion and returns the network with the subnet deleting. INVALID_STATE while instances that are not deleted use the subnet, with their ids in details.instanceIds, or load balancers are on it (details.loadBalancerIds), and while shared provider volumes of the network that are not deleted are in the subnet's region (a share lives in one of the network's VPCs there), with their ids in details.volumeIds. Its range is free for a new subnet at once. Decided as compute:networks:update on the network.

  • Kind: mutation
  • Authorization: compute:networks:update @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
subnetIdstringyesat most 57 characters; matches ^snet_[0-9a-hjkmnp-tv-z]{26,52}$

adopt

POST /api/compute/networks/adopt

Registers a VPC that already exists in the pool's provider account as a network of the tenant, so attached VMs whose NIC is on it are linked to it and get its firewall rules; returns the network pending, with externalNetwork set. Only the platform (root callers) adopts.

  • Input. externalNetwork is the VPC's provider-side name, 1 to 63 characters [a-z]([-a-z0-9]*[a-z0-9])?; a bc- name (a network better-compute made) is INVALID_INPUT. default is shared with every VM of the project: adopting it needs sharedTagScoped: true (INVALID_INPUT with details.field: 'sharedTagScoped' otherwise), which the network records and shows. name and description are as for create, and the 10-network cap applies.

  • The VPC. better-compute never creates, changes or deletes an adopted VPC: the reconciler skips it, provisions only the subnets and rules added to the network, and delete removes those and leaves the VPC. Its provider-side name is externalNetwork, not bc- and the id, also for instances created on it.

  • Firewall rules. A rule on an adopted network must name targets.instanceIds (INVALID_INPUT on firewallRules.create and update otherwise), so it always targets instance network tags and never the whole VPC. A rule without targets that reaches the reconciler anyway goes to error instead of being applied.

  • Attach. instances.attach links a machine whose NIC is on the VPC (by its name, or a reference ending in it) to the network, as it does for networks better-compute made.

  • Idempotency. The same adoption again (same pool, VPC, name, flag and description) returns the network. A VPC already adopted in the tenant under another request is CONFLICT with details.networkId; one adopted by another tenant is CONFLICT unless both adoptions are sharedTagScoped.

  • IAM. Decided as compute:providers:manage on the fleet (compute-fleet, in the root tenant); anyone but the platform is ACCESS_DENIED. The adopting identity owns the network.

  • Events. network.adopted with poolId, providerAccountId, name, externalNetwork and sharedTagScoped.

  • Kind: mutation

  • Authorization: compute:providers:manage @ compute-fleet

  • Module: vps

  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
poolIdstringyesat most 57 characters; matches ^pool_[0-9a-hjkmnp-tv-z]{26,52}$
externalNetworkstringyesat most 63 characters; matches ^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$
namestringyesat most 42 characters; matches ^[a-z]([-a-z0-9]{0,40}[a-z0-9])?$
sharedTagScopedbooleandefault false
descriptionstringat most 256 characters

natGateway

POST /api/compute/networks/natGateway

Turns a subnet's NAT gateway on (enabled: true) or off (enabled: false) and returns the network; the gateway appears in natGateways (pending, then ready with its public egress addresses). A NAT gateway carries the subnet's outbound traffic, so its machines need no public address of their own (isolated pools, and DOKS isolated worker nodes). Only providers with NAT gateways take it (DigitalOcean does; Google Cloud is UNSUPPORTED here).

  • One gateway per subnet: asking again returns the network unchanged, and turning off a subnet without one is a no-op.
  • Turning it off is INVALID_STATE, with the route names in details.routes, while a route of the subnet sends traffic to it (networks.routes): change those routes first.
  • The subnet must belong to the network and not be being removed (NOT_FOUND, INVALID_STATE); a network being deleted is INVALID_STATE.
  • Removing the subnet or deleting the network removes the gateway first: the reconciler deletes the subnet only once the gateway is gone.

Publishes network.natGateway.ready (and network.natGateway.error) with edgeId and subnetId. Decided as compute:networks:update on the network.

  • Kind: mutation
  • Authorization: compute:networks:update @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
subnetIdstringyesat most 57 characters; matches ^snet_[0-9a-hjkmnp-tv-z]{26,52}$
enabledbooleanyes

peer

POST /api/compute/networks/peer

Peers a subnet of the network (subnetId) with a subnet of another network of the tenant (peerNetworkId, peerSubnetId), so their machines reach each other on private addresses; enabled: false removes the peering. Two subnets of the same network may be peered too: on DigitalOcean every subnet is a VPC of its own, and two of them do not reach each other otherwise. Only providers with peerings take it (UNSUPPORTED otherwise).

  • The peering appears in peerings on both networks, each seen from its own side (subnetId is that network's subnet). The same pair asked again, from either network, is the same peering; either network can remove it.
  • Both networks must be the tenant's (another tenant's network is NOT_FOUND), on the same provider account (INVALID_INPUT), and not being deleted (INVALID_STATE). A subnet is never peered with itself (INVALID_INPUT).
  • A network takes part in at most 16 peerings (QUOTA_EXCEEDED, with details.limit and the network in details.networkId).
  • Removing either subnet, or deleting either network, removes the peering first; the subnet goes once the peering is gone.

Publishes network.peering.ready (and network.peering.error) on the network that asked first. Decided as compute:networks:update on the network, and, on the caller's own credential, on the other network too.

  • Kind: mutation
  • Authorization: compute:networks:update @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
subnetIdstringyesat most 57 characters; matches ^snet_[0-9a-hjkmnp-tv-z]{26,52}$
peerNetworkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
peerSubnetIdstringyesat most 57 characters; matches ^snet_[0-9a-hjkmnp-tv-z]{26,52}$
enabledbooleandefault true

routes

POST /api/compute/networks/routes

Replaces the static routes of a subnet with routes (at most 32) and returns the network; the routes appear in routes. A route kept exactly as it was stays; a changed one is replaced (deleted at the provider, then made anew); one left out is deleted; an empty list removes them all. Each route has a name (unique in the subnet), a destination IPv4 CIDR written as its network address (0.0.0.0/0 for everything), and exactly one nextHop:

  • { natGateway: true }: the subnet's NAT gateway, which must exist (INVALID_STATE otherwise). The reconciler makes the route once the gateway is ready.
  • { instanceId }: an instance of the tenant on this subnet (NOT_FOUND, or INVALID_INPUT when it is on another subnet). The route waits until the instance has a machine; an instance that is gone later puts the route in error. Decided, besides the network, as compute:instances:update on the instance, on the caller's own credential.
  • { address }: a host address inside the subnet (INVALID_INPUT otherwise).

Routes are a preview at DigitalOcean (VPC Routes): the account needs previews: true, or the route goes to error with the provider's refusal. Only providers with static routes take a non-empty list (UNSUPPORTED otherwise).

Publishes network.route.ready (and network.route.error). Decided as compute:networks:update on the network.

  • Kind: mutation
  • Authorization: compute:networks:update @ compute-network
  • Module: vps
  • Phase: v1.0 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
networkIdstringyesat most 56 characters; matches ^net_[0-9a-hjkmnp-tv-z]{26,52}$
subnetIdstringyesat most 57 characters; matches ^snet_[0-9a-hjkmnp-tv-z]{26,52}$
routeslist of objectyes0-32 items
routes[].namestringyesat most 42 characters; matches ^[a-z]([-a-z0-9]{0,40}[a-z0-9])?$
routes[].destinationstringyes
routes[].nextHopobjectyes
routes[].nextHop.natGatewayany
routes[].nextHop.instanceIdstringat most 57 characters; matches ^inst_[0-9a-hjkmnp-tv-z]{26,52}$
routes[].nextHop.addressstring

Was this page helpful?

Better Compute is created by Sean Filimon

Last updated

On this page