CLI project selectors omitted for clarity.
I'm writing this mainly to figure out what we want for the initial floating IP attach/detach API from our discussion earlier in the week. We need to balance usability (API, CLI) with space for future features tthat will build directly on this.
Having written this out now, I'm leaning towards Prop.3 for floating IP atach/detach, and /v1/instances/{instance}/external-ips/ephemeral
for ephemeral IP add/delete.
oxide floating-ip {list,create,view,delete}
- GET
/v1/floating-ips
- POST
/v1/floating-ips
- GET
/v1/floating-ips/{floating_ip}
- DELETE
/v1/floating-ips/{floating_ip}
- GET
oxide instance external-ip list --instance xxx
will list all bound to a target instance- GET
/v1/instances/{instance}/external-ips
- Today:
[Ephemeral(IpAddr) | Floating(IpAddr)]
- Omicron#4694:
[Ephemeral(IpAddr) | Floating(FloatingIp)]
- Today:
- GET
FloatingIp
contains all identity metadata, and refers to attached instance viaparent_id: Option<Uuid>
./v1/instances/{instance}/external-ips/[attach,detach]
is determined as the wrong expression for FIPs, and leads to a deeply nested CLI that needs manual command specification.- We know that Floating IPs will have several targets: instances today, silo services in future, and other project-scoped resources like load balancers and Internet gateways.
/v1/floating-ips/{floating_ip}/attach
- POST
{ "instance": "<name_or_id>" }
-> FloatingIp - POST
{ "service": "<name>" }
-> FloatingIp - POST
{ "load-balancer": "<name_or_id>" }
-> FloatingIp - POST
{ "gateway": "<name_or_id>" }
-> FloatingIp
The above is captured with a body type like the below:
enum FloatingIpAttach {
Instance(NameOrId),
Service(Name), // won't be present today.
LoadBalancer(NameOrId), // won't be present today.
InternetGateway(NameOrId), // won't be present today.
}
/v1/floating-ips/{floating_ip}/detach
- POST
<empty>
-> FloatingIp
-
oxide floating-ip attach --floating-ip xxx --instance yyy
-
oxide floating-ip attach --floating-ip xxx --service dns
-
oxide floating-ip attach --floating-ip xxx --service nexus
-
oxide floating-ip detach --floating-ip xxx
However, using the above TypedBody
/FloatingIpAttach
does not autogenerate a useful API. We instead get:
Usage: oxide floating-ip attach [OPTIONS] --floating-ip <floating-ip> --json-body <JSON-FILE>
Options:
--floating-ip <floating-ip> Name or ID of the Floating IP
--project <project> Name or ID of the project
--json-body <JSON-FILE> Path to a file that contains the full json body.
--json-body-template XXX
-h, --help Print help
Detach works nicely though.
Pros/cons:
- (+) One endpoint for each 'verb', so REST API feels tidier to me.
- (-) Most users should not be assigning FIPs to services, so maybe it shouldn't have the same 'visibility'.
- (-) CLI needs manual endpoint insertion/maintenance.
/v1/floating-ips/{floating_ip}/attach
- POST
{ "instance": "<name_or_id>" }
-> FloatingIp
/v1/floating-ips/{floating_ip}/attach-service
- POST
{ "service": "<name>" }
-> FloatingIp
/v1/floating-ips/{floating_ip}/detach
- POST
<empty>
-> FloatingIp
-
oxide floating-ip attach --floating-ip xxx --instance yyy
-
oxide floating-ip attach service --floating-ip xxx --service dns
-
oxide floating-ip attach service --floating-ip xxx --service nexus
-
oxide floating-ip detach --floating-ip xxx
Pros/cons:
- (+) These autogenerate nicely with the CLI, including the
attach service
subcommand. - (+) Service attach will require silo admin access I'd imagine, so maybe splitting this out from 'regular' calls makes that more explicit.
- (+) Gives us space to include more parameters for Services -- we don't know exactly what service attach will need yet (and this may vary on a per-service basis).
- ( ) The 'happy path' has the simpler syntax for both CLI/API.
- (-) Attachment to other project-scoped objects like load balancers will also be common, and delegating to another command feels unsatisfying.
- (-) This feels a bit more untidy as an API.
/v1/floating-ips/{floating_ip}/attach
- POST
{ "type": "instance", "parent": "<name_or_id>" }
-> FloatingIp - POST
{ "type": "service", "parent": "<name>" }
-> FloatingIp - POST
{ "type": "load-balancer", "parent": "<name_or_id>" }
-> FloatingIp - POST
{ "type": "gateway", "parent": "<name_or_id>" }
-> FloatingIp
via:
struct FloatingIpAttach {
parent: NameOrId,
type: FloatingIpAttachKind,
}
enum FloatingIpAttachKind {
Instance,
Service, // won't be present today.
LoadBalancer, // won't be present today.
InternetGateway, // won't be present today.
}
/v1/floating-ips/{floating_ip}/detach
- POST
<empty>
-> FloatingIp
-
oxide floating-ip attach --floating-ip xxx --type instance --parent xxx
-
oxide floating-ip attach --floating-ip xxx --type service --parent dns
-
oxide floating-ip attach --floating-ip xxx --type service --parent nexus
-
oxide floating-ip detach --floating-ip xxx
Pros/cons:
- (+) These autogenerate okay.
- (+) API itself has fewer endpoints for the same operation.
- (+) Puts the attach operations which reference objects likely to be regularly created in the user-facing API on equal footing (instances, loadbalancers, gateways).
- ( ) If instances are the most common target, we could make
type
optional in the CLI. It should always be specified in the API. - ( ) Nothing stopping us from breaking out services into a separate command if extra flags etc needed, or we feel the separation of access rights should be highlighted.
- (-) Autogenerated CLI is noisy. We could put in custom parse logic via
OxideOverride
to attempt to simplify.
/v1/floating-ips/{floating_ip}/parent
- PUT
{ "instance": "<name_or_id>" }
-> FloatingIp (attach instance) - PUT
{ "service": "<name?>" }
-> FloatingIp (attach service) - PUT
<empty>
-> FloatingIp (detach)
Alternatively, we could PUT/PATCH on the floating-ip resource itself (i.e., via the parent
field).
I don't think this would be at all ergonomic in CLI.
I'm a little bit fuzzier on what this should look like:
/v1/instances/{instance}/external-ips/ephemeral
- GET ->
Option<ExternalIp>
- POST
{"pool": "<name_or_id>"}
-> ExternalIp - DELETE -> Ok()
We could have these as separate endpoints using POST, but I think that would be awful:
/v1/instances/{instance}/external-ips/add-ephemeral
/v1/instances/{instance}/external-ips/delete-ephemeral
oxide instance external-ip add-ephemeral --instance xxx --pool yyy
oxide instance external-ip delete-ephemeral --instance xxx --pool yyy
Pros/cons:
- (+) Default behaviour is that users will receive an ephemeral IP, and this API is not a necessity to receive one.
- Ephemeral should be dealloc'd + realloc's on instance start stop
- API consideration:
/v1/instances/{instance}/external-ips
and related should changeEphemeral(IpAddr)
->Ephemeral(Option<IpAddr>)
- API consideration:
- Effective A->B attach without detach for, e.g., blue-green deployment.
- will likely require extra OPTE work to guarantee there's always an instance listening to traffic.
- should active-passive use be left as a consideration for load balancers and/or DNS? This suggests multiple parents which is a bit of a shift from where we are today.
- Non-instance parents of floating IPs:
- API consideration:
FloatingIp::parent_id
should changeOption<Uuid>
->Option<Parent>
, withenum Parent {Instance(Uuid), Service(...), ...}
. This would make Proposal 4 more infeasible, but I'm not sure that's even in the running.
- API consideration:
- Ephemeral upgrade to float:
/v1/instances/{instance}/external-ips/upgrade-ephemeral
feels bad -- it could possibly be specified as a source parameter on the existing FIP create API?
- GET
/v1/floating-ips/available
to list unused FIPs in a project? - Resource limit for 'IPs in use by a project'.