Skip to content
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

Add IP Pools and contained IP ranges #1253

Merged
merged 6 commits into from
Jun 24, 2022
Merged

Add IP Pools and contained IP ranges #1253

merged 6 commits into from
Jun 24, 2022

Conversation

bnaecker
Copy link
Collaborator

  • Adds basic CRUD API for IP Pools, as named, global resources
  • Adds table and database operations for IP Pools
  • Adds API for operating on IP ranges within an IP Pool. These are
    expressed as a first/last IP address pair, and describe a range of
    addresses to add or remove from a pool. Currently, all ranges must be
    non-overlapping, which is checked at insertion time with a custom
    query. Deleting a range currently requires specifying the exact same
    first/last address used to create it. Both of these could be relaxed
    in the future without changing the API, for example to support
    splitting or coalescing ranges.
  • Adds paginated endpoint for listing the ranges within a pool, ordered
    by the first address in the range.
  • Adds integration tests for new endpoints.
  • Adds unauthorized coverage checks.

- Adds basic CRUD API for IP Pools, as named, global resources
- Adds table and database operations for IP Pools
- Adds API for operating on IP ranges within an IP Pool. These are
  expressed as a first/last IP address pair, and describe a range of
  addresses to add or remove from a pool. Currently, all ranges must be
  non-overlapping, which is checked at insertion time with a custom
  query. Deleting a range currently requires specifying the exact same
  first/last address used to create it. Both of these could be relaxed
  in the future without changing the API, for example to support
  splitting or coalescing ranges.
- Adds paginated endpoint for listing the ranges within a pool, ordered
  by the first address in the range.
- Adds integration tests for new endpoints.
- Adds unauthorized coverage checks.
@smklein smklein self-assigned this Jun 23, 2022
Copy link
Collaborator

@smklein smklein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, nice job getting this integrated!

* Note that these need not be CIDR blocks or well-behaved subnets with a
* specific netmask.
*/
CREATE TABLE omicron.public.ip_pool_range (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it comes time to associate external IPs with instances (or services, like Nexus), do we expect to just use this table as-is, or create a new representation of "assigned" IP addresses?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure what that'll look like, but I've had some thoughts. TL;DR: I'm expecting to add the used IPs into things like the network_interface table.

So the IP Pools that we've implemented here are a bit of a conflation of two things: IP Pools as defined by RFD 21 and "address sets" defined by RFD 267. The former are addresses used for instance NAT. The latter are more general -- they are a superset of IP Pools, but also used for things like services (e.g, Nexus, DNS) and customer network integration (such as prefixes advertised in some routing protocol like BGP). The work here unfortunately merges these, because we need to get something and it's not entirely clear what's required for address sets yet.

That said, I'd expect that the address set table(s) will track what each set is for, and which will direct us to another table, say the ip_pool table, for finding the ranges within that set. Specifically for IP Pools in the sense of instance NAT, I do expect that we'll update the network_interface table (or add a new table) to include the IPs from this range that are currently in use. It all gets pretty hairy here in terms of the database operations, since now there are at least 3 tables to consider for certain operations, such as deleting an IP Pool resource.

common/src/api/external/mod.rs Outdated Show resolved Hide resolved
common/src/api/external/mod.rs Outdated Show resolved Hide resolved
common/src/api/external/mod.rs Outdated Show resolved Hide resolved
common/src/api/external/mod.rs Show resolved Hide resolved
nexus/src/external_api/http_entrypoints.rs Outdated Show resolved Hide resolved
nexus/src/external_api/http_entrypoints.rs Outdated Show resolved Hide resolved
nexus/tests/integration_tests/ip_pools.rs Outdated Show resolved Hide resolved
nexus/src/external_api/http_entrypoints.rs Outdated Show resolved Hide resolved
nexus/tests/integration_tests/ip_pools.rs Show resolved Hide resolved
@smklein smklein removed their assignment Jun 23, 2022
openapi/nexus.json Outdated Show resolved Hide resolved
bnaecker added 3 commits June 23, 2022 18:06
- Constructors and getters for IP range types to ensure they are
  non-decreasing
- Tests of IP range types
- Better conversions between model / view / public IP range types
- Remove lots of dead code, some comment cleanup
- Rename some types to avoid OpenAPI confusion
@david-crespo
Copy link
Contributor

david-crespo commented Jun 23, 2022

Thanks for fixing the OpenAPI schema thing. Running this through the TS generator, there's no way to distinguish between IPv4 and IPv6 because of TS's structural typing. Usually we use a discriminator property to discriminate, like adding type: "ipv4" and type: "ipv6" respectively. I don't think there's anything you need to change here, because adding such a discriminator would not make sense in the Rust code. I will mull over whether I think Dropshot's OpenAPI spec generator should try to handle this, or maybe it's something we can automatically do in the TS generator whenever we see enums of a certain kind.

image

@bnaecker
Copy link
Collaborator Author

@david-crespo Sure thing, thanks for flagging it. What does the TS generator do for IpNet now? That works the same way, as an externally-tagged enum, e.g. {"V4": {"first": .., "last":, .... }}.

@david-crespo
Copy link
Contributor

david-crespo commented Jun 23, 2022

😁

type IpNet = Ipv4Net | Ipv6Net

type Ipv4Net = string

type Ipv6Net = string

So far it has not been a problem. I'll keep an eye on it. We also pull in the regex pattern for validating each one at runtime, but the types don't know anything about that.

@bnaecker
Copy link
Collaborator Author

Welp, that's a bummer 😆 . We can make them internally-tagged, e.g., the type/value keys that you referred to earlier. I don't really have a horse in the race -- it's all the same once we deserialize into Rust, so whatever makes the clients work better. @ahl Any suggestions here?

common/src/api/external/mod.rs Outdated Show resolved Hide resolved
nexus/src/authz/api_resources.rs Outdated Show resolved Hide resolved
nexus/src/external_api/http_entrypoints.rs Outdated Show resolved Hide resolved
nexus/src/external_api/params.rs Outdated Show resolved Hide resolved
nexus/src/external_api/params.rs Outdated Show resolved Hide resolved
common/src/sql/dbinit.sql Show resolved Hide resolved
common/src/sql/dbinit.sql Outdated Show resolved Hide resolved
nexus/src/db/model/ip_pool.rs Show resolved Hide resolved
nexus/src/db/model/ip_pool.rs Show resolved Hide resolved
nexus/src/db/datastore.rs Outdated Show resolved Hide resolved
@ahl
Copy link
Contributor

ahl commented Jun 23, 2022

As I recall, we drew inspiration from std::net::IpAddr which serializes to a string: https://docs.serde.rs/src/serde/ser/impls.rs.html#654-675

And the JsonSchema for it is just: I'm a string but with a format = ip
https://docs.rs/schemars/latest/src/schemars/json_schema_impls/primitives.rs.html#52

For IpNet we had the actual regex for v4/v6 so included them in the schema (@bnaecker I think you did this work). It's probably more than is strictly necessary.

impl JsonSchema for IpNet {
fn schema_name() -> String {
"IpNet".to_string()
}
fn json_schema(
gen: &mut schemars::gen::SchemaGenerator,
) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
metadata: Some(
schemars::schema::Metadata { ..Default::default() }.into(),
),
subschemas: Some(
schemars::schema::SubschemaValidation {
one_of: Some(vec![
label_schema("v4", gen.subschema_for::<Ipv4Net>()),
label_schema("v6", gen.subschema_for::<Ipv6Net>()),
]),
..Default::default()
}
.into(),
),
..Default::default()
}
.into()
}
}

As an aside, progenitor recovers the the type pretty well, but doesn't properly include the custom regex for deserialization:
https://github.com/oxidecomputer/progenitor/blob/main/progenitor-impl/tests/output/nexus.out#L566-L571

    #[derive(Serialize, Deserialize, Debug, Clone)]
    #[serde(untagged)]
    pub enum IpNet {
        V4(Ipv4Net),
        V6(Ipv6Net),
    }
    #[derive(Serialize, Deserialize, Debug, Clone)]
    pub struct Ipv4Net(pub String);

Part of the motivation here is to let consumers of the API simply say:

{
    "ip": "127.0.0.1"
}

Rather than

{
    "ip": {
        "v4": "127.0.0.1"
    }
}

... or something like that.

I think ideally a generator is going to either distinguish types based on these regexes or do a "deep collapse" i.e. saying

enum IpRange {
    IpV4Range {
        first: String,
        last: String,
    },
    IpV6Range {
        first: String,
        last: String,
    },
}

should be collapsed into

struct IpRange {
    first: String,
    last: String,
}

So either we should:

  • drop all the fancy regex stuff and just say that these types are strings
  • update the generators to either handle the regexes all the way or reduce structurally equivalent types

@ahl
Copy link
Contributor

ahl commented Jun 23, 2022

Also for context (at least for myself):
@smklein proposed switching to ipnet from ipnetwork #877 and to that end he added JsonSchema support krisprice/ipnet#41. Support for JsonSchema in ipnetwork appears to be busted achanda/ipnetwork#155 (I am going to investigate and file a bug).

@david-crespo
Copy link
Contributor

From TS's POV it is already collapsed because the following are equivalent:

type X = { first: string; last: string } | { first: string; last: string }

type Y = { first: string, last: string }

@bnaecker
Copy link
Collaborator Author

Thanks Adam, that's helpful. I would argue we don't use the regular expressions for determining types. There has already been at least one bug, and I'm betting there are more. The server will ultimately parse the request data anyway, rather than validate they match the regex, so those errors will just be annoying for clients.

One option here is to not have an enum, but use a generic (@davepacheco proposed this above). For the range, it'd be something like IpRange<Ipv4Addr> and IpRange<Ipv6Addr>. The Nexus API would take IpRange<T>, although I don't know if that works with Dropshot's endpoint macro.

bnaecker added 2 commits June 23, 2022 23:27
- Move IpRange types to shared Nexus crate
- Remove more dead code
- Cleanup some comments
- Remove unnecessary pagination order from ip pool ranges
- Remove more dead code
- Untagged enum repr for IpRange type
@bnaecker
Copy link
Collaborator Author

@smklein @davepacheco @david-crespo Thanks all for the helpful input! Let me know if y'all have any further comments.

@bnaecker bnaecker merged commit 89e5575 into main Jun 24, 2022
@bnaecker bnaecker deleted the ip-pool-crud branch June 24, 2022 16:10
iliana added a commit that referenced this pull request Jun 24, 2022
davepacheco pushed a commit that referenced this pull request Jun 24, 2022
leftwo pushed a commit that referenced this pull request Apr 19, 2024
Propolis changes:
Update h2 dependency
Add NPT ops API definitions from illumos#15639
server: return better HTTP errors when not ensured (#649)

Crucible changes:
Make Region test suite generic across backends (#1263)
Remove async from now-synchronous functions (#1264)
Agent update to support cloning. (#1262)
Remove the Active → Faulted transition (#1260)
Avoid race condition in crutest rand-read/write (#1261)
Add Active -> Offline -> Faulted tests (#1257)
Reorganize dummy downstairs tests (#1253)
Switch to unbounded queues (#1256)
Add Upstairs session ID to dtrace stat probe, cleanup closure (#1254)
Panic instead of returning errors in unit tests (#1251)
Add a clone option to downstairs create (#1249)
leftwo added a commit that referenced this pull request Apr 19, 2024
Propolis changes:
Update h2 dependency
Add NPT ops API definitions from illumos#15639
server: return better HTTP errors when not ensured (#649)

Crucible changes:
Make Region test suite generic across backends (#1263) Remove async from
now-synchronous functions (#1264) Agent update to support cloning.
(#1262)
Remove the Active → Faulted transition (#1260)
Avoid race condition in crutest rand-read/write (#1261) Add Active ->
Offline -> Faulted tests (#1257)
Reorganize dummy downstairs tests (#1253)
Switch to unbounded queues (#1256)
Add Upstairs session ID to dtrace stat probe, cleanup closure (#1254)
Panic instead of returning errors in unit tests (#1251) Add a clone
option to downstairs create (#1249)

Co-authored-by: Alan Hanson <alan@oxide.computer>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants