-
Notifications
You must be signed in to change notification settings - Fork 62
Updates default VPC Subnet behavior #677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This improves the API and database internals around VPC Subnets, specifically the how we create the default VPC Subnet for a VPC, and how IP address ranges are specified by clients or generated internally. It also adds a query that checks for overlapping address ranges, and fails the insert of a new VPC Subnet if an existing subnet in the same VPC has overlapping IP address ranges, either IPv4 or IPv6.
|
Some more details on this change. There's been some confusion (mostly in my own head!) about how the IP address ranges for VPC Subnets are specified. RFD 21 originally described both V4 and V6 ranges as optional, but did not get into what the behavior should be if neither is specified. We resolved this originally by allowing optional values in the request to create a VPC, but we always unwrapped the V4 block in any case. The requirements for the MVP have shifted since RFD 21 was written, and we're now requiring that all subnets have both IPv4 and IPv6 address ranges. Clients must specify the V4 range when creating a new VPC Subnet, since there's no reasonable default. However, we allow V6 to remain unspecified in the request, and generate a random unique-local address, from the VPC's IPv6 prefix, in that case. Note that the one exception is the default VPC Subnet, created when the default VPC for a project is created. We use a default of I've updated RFD 21 to reflect these changes, and this change is mostly to implement those new expectations. The bulk of the LOC, however, is for handling overlapping IP address ranges. We previously checked for name conflicts, like any other resource, but there was nothing preventing two VPC Subnets actually had distinct IP ranges. This introduces a new query to handle this. The query operates by taking the "candidate" row, including the requested IP ranges, and possibly filtering it out by checking the I've verified that this is a reasonable query, in that it uses this index to search for records with a given VPC ID. The full explain analyze output of the CTE searching the There's the line in the This commit also generates different error messages for the client, depending on the type of "conflict". This shows the output of creating a new VPC Subnet with some invalid data. In the first two requests, the IPv4 or IPv6 subnet range overlaps, respectively. In the third, the ranges are fine, but the name conflicts. bnaecker@shale : ~/omicron $ curl --request POST http://127.0.0.1:12220/organizations/my-org/projects/my-project3/vpcs/default/subnets -H "oxide-authn-spoof: 001de000-05e4-4000-8000-000000004007" -d '{"name": "default", "description": "howdy do", "ipv4_block": "172.30.0.0/22", "ipv6_block": "fd50:c734:ddaf::/64"}'
{
"request_id": "69f7b60e-8e55-4cd4-bc3e-949b441bf15d",
"error_code": "InvalidRequest",
"message": "IP address ranges must not overlap for subnets within a VPC"
}
bnaecker@shale : ~/omicron $ curl --request POST http://127.0.0.1:12220/organizations/my-org/projects/my-project3/vpcs/default/subnets -H "oxide-authn-spoof: 001de000-05e4-4000-8000-000000004007" -d '{"name": "default", "description": "howdy do", "ipv4_block": "172.30.0.0/22", "ipv6_block": "fd50:c734:ddae::/64"}'
{
"request_id": "61d0ebba-c08f-4c01-97de-291e0ef1252d",
"error_code": "InvalidRequest",
"message": "IP address ranges must not overlap for subnets within a VPC"
}
bnaecker@shale : ~/omicron $ curl --request POST http://127.0.0.1:12220/organizations/my-org/projects/my-project3/vpcs/default/subnets -H "oxide-authn-spoof: 001de000-05e4-4000-8000-000000004007" -d '{"name": "default", "description": "howdy do", "ipv4_block": "172.31.0.0/22", "ipv6_block": "fd50:c734:ddae::/64"}'
{
"request_id": "011bff6e-2e08-48cb-9b90-596f016fa9bd",
"error_code": "ObjectAlreadyExists",
"message": "already exists: vpc-subnet \"default\""
}Unfortunately, there's no way with the current code to catch both these, or to catch the name conflict first. That's because the CTE to filter the candidate is run first. So if the name conflicts, but so do the IP ranges, there's no row and the constraint violation on the name is not hit. I don't think this is a huge drawback, but it might be a bit surprising. One other annoyance arises in the case where the IPv6 range is not specified. We generate a default, random ULA from the prefix of the VPC itself. This is a In this case, we return a 503, the intent being that another attempt is another dice roll, which will reduce the collision probability to |
|
Well, that's sad. The error here is apparently a platform difference in how chrono renders nanosecond timestamps :/ |
d1d7946 to
0299cbf
Compare
davepacheco
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this change. It sounds like it'll close up a bunch of important edge cases.
This is not a complete review but I wanted to get this feedback sooner rather than later.
- Clarifies ULA checks, and adds methods for explictly checking VPC prefix validity and subnet validity for a given VPC. - Simplifies random subnet generation using big-endian representation - Adds more tests to random subnet generation, including fixed seed to verify random bits - Adds test verifying that the query used to filter conflicting VPC Subnets does not induce a full table scan - Adds a SubnetError type which can be returned from an attempt to insert a unique VPC Subnet. This has a variant that indicates there was a conflict in either the IPv4 or v6 address ranges, supporting retries and better errors for the client. - Adds retry loop around the code that generates a random IPv6 subnet for a VPC Subnet, if users don't supply on in the request. This creates a random subnet based on the VPC's IPv6 prefix, and tries to insert it using the filtering query. If this fails some small number of times, currently 3 total, then a 503 is returned to the client. We also log a warning in this case.
This comment was marked as resolved.
This comment was marked as resolved.
| { | ||
| fn walk_ast(&self, mut out: AstPass<Pg>) -> QueryResult<()> { | ||
| out.push_sql("EXPLAIN ("); | ||
| out.push_sql("EXPLAIN "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, why this change? I typically do use parentheses around stuff like this for both clarity and in case it gets composed into something else that would change the interpretation (though that latter case doesn't seem likely here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. The test I added here fails with a syntax error if parentheses are used. I agree that it seems extras should be harmless, but the documentation and examples for EXPLAIN don't seem to use them anywhere around the expression to be explained. It seems that parentheses are used to define the kind of explanation, e.g., what types are inferred or how the optimizer assigns costs.
This improves the API and database internals around VPC Subnets,
specifically the how we create the default VPC Subnet for a VPC, and how
IP address ranges are specified by clients or generated internally.
It also adds a query that checks for overlapping address ranges, and
fails the insert of a new VPC Subnet if an existing subnet in the same
VPC has overlapping IP address ranges, either IPv4 or IPv6.