Validated types for expose, manifest, peering, VPC, etc.#1505
Validated types for expose, manifest, peering, VPC, etc.#1505
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces “validated” wrapper types across the overlay/VPC/expose configuration model (and downstream consumers) so that post-validation invariants are represented in the type system. It also moves exclusion-prefix collapsing earlier (during validation) so consumers reuse pre-collapsed prefix sets instead of recomputing them in multiple places.
Changes:
- Add validated newtypes (e.g.,
ValidatedGwConfig,ValidatedOverlay,ValidatedVpcTable,ValidatedPeering,ValidatedManifest,ValidatedExpose) and update mgmt/routing/NAT/flow-filter to consume them. - Collapse exclusion prefixes once during expose validation and propagate the collapsed view (
valexp) through manifests/peerings/VPCs. - Update tests and dev-dependencies to use testing-only helpers for minting validated wrappers where configs are intentionally invalid.
Reviewed changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| validator/src/main.rs | Adjusts config validation call site to new GwConfig::validate API. |
| routing/src/router/rio.rs | Stores applied config as Arc<ValidatedGwConfig> in router runtime state. |
| routing/src/router/ctl.rs | Updates control messages/APIs to pass validated config types. |
| routing/src/cli/handler.rs | Updates CLI “show config” paths to use validated getters. |
| nat/src/stateless/test.rs | Migrates stateless NAT tests to ValidatedGwConfig and validated peerings. |
| nat/src/stateless/setup/mod.rs | Stateless NAT table building now consumes validated VPC/peering/expose types. |
| nat/src/stateful/test.rs | Migrates stateful NAT tests to validated config/overlay accessors. |
| nat/src/stateful/flows.rs | Updates flow checks to use validated manifests/exposes. |
| nat/src/stateful/apalloc/test_alloc.rs | Updates allocator tests to use validated VPC tables and validated peering data. |
| nat/src/stateful/apalloc/setup.rs | Updates allocator setup to use validated peering/manifest/expose accessors. |
| nat/src/stateful/allocator_writer.rs | Makes StatefulNatConfig derived from ValidatedVpcTable and stores validated peerings. |
| nat/src/portfw/portfwtable/setup.rs | Port-forwarding table builder now consumes validated overlay/VPC/peering/expose types. |
| nat/Cargo.toml | Enables config’s testing feature for NAT crate tests. |
| mgmt/src/tests/mgmt.rs | Updates mgmt test utility to validate and use ValidatedGwConfig. |
| mgmt/src/processor/proc.rs | Processor pipeline now validates upfront and applies ValidatedGwConfig throughout. |
| mgmt/src/processor/mgmt_client.rs | Exposes “current config” as Arc<ValidatedGwConfig>. |
| mgmt/src/processor/k8s_client.rs | Updates status timestamp reads to use validated meta accessor. |
| mgmt/src/processor/gwconfigdb.rs | Stores applied config as Arc<ValidatedGwConfig> and seeds blank validated config. |
| mgmt/src/processor/confbuild/router.rs | Router config generation now takes validated config. |
| mgmt/src/processor/confbuild/internal.rs | Internal config build now consumes ValidatedGwConfig/ValidatedOverlay. |
| lpm/src/prefix/with_ports.rs | Adds prefix-set size utility used by validation; makes ppsize_zero const fn. |
| flow-filter/src/tests.rs | Updates tests to use validated overlays; uses test-only unsafe validation escapes for invalid configs. |
| flow-filter/src/setup.rs | Flow-filter table build now consumes ValidatedOverlay and uses validated manifests/exposes. |
| flow-filter/Cargo.toml | Enables config’s testing feature for flow-filter crate tests. |
| config/src/utils/overlap.rs | Overlap checking now operates on validated (already-collapsed) expose prefix sets. |
| config/src/utils/mod.rs | Updates collapse helper export visibility/name to collapse_prefixes. |
| config/src/utils/collapse.rs | Refactors collapse helper to operate at expose level; updates tests accordingly. |
| config/src/lib.rs | Re-exports ValidatedGwConfig from the config crate. |
| config/src/gwconfig.rs | Adds ValidatedGwConfig wrapper; changes GwConfig::validate to consume and return validated type. |
| config/src/external/overlay/vpcpeering.rs | Adds ValidatedExpose/ValidatedManifest; validates/collapses exposes into valexp. |
| config/src/external/overlay/vpc.rs | Adds validated wrappers for peering/VPC/VPC table; validation now populates validated views. |
| config/src/external/overlay/validation_tests.rs | Updates tests for new validation return types and semantics. |
| config/src/external/overlay/tests.rs | Updates overlay tests for new manifest/peering validation APIs and messages. |
| config/src/external/overlay/mod.rs | Adds ValidatedOverlay and test-only manifest validation bypass helper. |
| config/src/external/mod.rs | Adds ValidatedExternalConfig accessor wrapper used by ValidatedGwConfig. |
| config/src/display.rs | Adds display support for validated overlay/VPC/peering/expose structures. |
| config/Cargo.toml | Adds testing feature to expose test-only unsafe constructors in downstream crates’ tests. |
1ff9239 to
c6367d6
Compare
Introducing other types is doable, but means we need to re-build all the objects when we validate (copying all exposes and their prefixes, manifests, peerings, VPCs) to build the new ones. The performance hit is probably not too big, but I didn't really see any advantage in doing that. If you strongly feel for the new types, though, I can go that way.
Thank you! I'll leave it a couple more days in case somebody else wants to review, and then I'll click the green button. |
Add a method to return the total size of "prefixes" of a PrefixPortsSet, meaning the number of IP from the different prefixes multiplied by the number of ports from the respective associated port ranges. Mark ppsize_zero() as a const method. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Add a ValidatedExpose type that holds a validated view of a VpcExpose: exclusion prefixes (`nots`, `not_as`) have been applied, the "ips" and "as_range" lists are normalised, and all the size/structure invariants enforced by "VpcExpose::validate" hold. The object has private fields, and no builders, making it only available through VpcExpose::validate(). ValidatedExpose mirrors some of the existing methods from VpcExpose, where relevant. The objective of this wrapper is to guarantee some invariants via the type system, and also to help move some of the processing we do on the expose blocks - for example, the collapsing of the exclusion prefixes onto the allowed prefixes - to a single location, rather than multiple locations like we do today (when building the context for flow-filter and the different NAT modes). Signed-off-by: Quentin Monnet <qmo@qmon.net>
Add a ValidatedManifest transparent wrapper around VpcManifest, exposing
the contents of the manifest through a validated lens: callers can reach
the list of validated expose blocks ("valexp"), but they cannot modify
it. There is no way to obtain a ValidatedManifest currently. We'll
provide one from the parent validated object.
Simplify overlap detection functions:
check_private_prefixes_dont_overlap() and
check_public_prefixes_dont_overlap() now take a reference to a
ValidatedExpose, meaning we no longer have to deal with exclusion
prefixes.
Signed-off-by: Quentin Monnet <qmo@qmon.net>
After the validated types for expose blocks and manifests, keep going with a transparent wrapper around the Peering objects. It gets methods to return the validated manifests attached to the validated peering. Similarly, there's no way to obtain a validated Peering object yet: the parent object will return it. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Following up on validated types for expose blocks, manifests, and peering objects, add a transparent wrapper on top of the Vpc objects, and implement the methods that we'll need to preserve the logic. Method check_overlap_and_default() is moved to the new type, so that we can have it use the validated and processed list of expose blocks, which simplifies validation (and is actually more correct than the previous version, where we wouldn't account for exclusion prefixes!). Add an inner() method to the validated type: we'll need it at some locations where we prefer to work with the original type. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Keep going in our series of transparent "validated" wrapper types: here we wrap around the VpcTable. We can get validated Vpc objects from a validated Vpc table. The validated table itself will be available from a validated Overlay, in a follow-up commit. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Keep going with the new transparent "validated" wrapper types: this time, for the Overlay object. It contains a validated VPC table, and a regular peering table (as this object doesn't require, or inherit from, any particular validation). There's no builder for a validated Overlay: we'll obtain it from the parent object in a follow-up commit, just like the wrapper introduced in this commit is the only object able to return a validated VPC table. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Keep going with the new transparent "validated" wrapper types: introduce a new wrapper, this time for the ExternalConfig objects. Signed-off-by: Quentin Monnet <qmo@qmon.net>
At last, after introducing validated types for expose blocks, manifests, peerings, VPCs, VPC tables, overlay objects, external config objects, we come to the toplevel and last object in the series: the GwConfig object. We can create a blank validated configuration (we add a unit test to ensure it is actually valid, in a follow-up commit); or we'll be able to create one through validation of a GwConfig object, in a follow-up commit. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Add "validated()" methods to the validated versions of Overlay, Peering, VpcManifest, Vpc, VpcTable, and GwConfig. These are mostly for tests, where we need to build valid objects without going through the full GwConfig validation. As for GwConfig itself, the method will be used temporarily in a follow-up commit, for transitioning to the new types, before being removed in yet another commit. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Add a set of unsafe helpers that let tests in other crates fabricate validated wrappers without actually going through validation of the different objects. Obviously, these are extremely dangerous to handle, and we reserve them for tests only - this is enforced with a gating behind a new "testing" feature for the config crate. We'll need these helpers to adjust some existing tests where using "invalid" instances of some objects help us improve our code coverage. Signed-off-by: Quentin Monnet <qmo@qmon.net>
After the introduction of "validated" types for expose blocks, manifests, peerings, VPCs, VPC tables, overlays, external config objects, and gateway configuration objects, we reach the painful step where we need to propagate the changes everywhere these objects are in use. Switch the dataplane processing pipeline (mgmt, routing, validator), the flow-filter setup, and the NAT setup over to the new validated types. From this commit on, downstream code no longer handles at raw GwConfig, Overlay, VpcTable, VpcManifest, VpcExpose fields directly: it goes through the validated variants. Well, that's not entirely true: we don't do the switch for routing, for example (we fall back to the inner, original types), because the routing code does not need the processed expose blocks. This affects various crates: - In the "mgmt" processor, process_incoming_config() takes ownership of a GwConfig and runs its method validated() to obtain a validated object, then uses set_internal_config() to store it. This, with a "blank()" configuration (and apart from test-only unsafe helpers), are the only ways to obtain a validated configuration object. After that, internal builders walk the validated overlay and validated VPC table. - routing gets a validated configuration object, but the changes to that crate are minimal - validator: Similarly to the routing crate, we only get one type changed. - flow-filter gets more changes, in particular in the context setup where we use the various new types to build the flow-filter table. The tests also require a number of adjustments, including the use of the unsafe helpers to fake valid types at a few locations. We get rid of the collapse_prefixes_peering() call, now that the exclusion prefixes have been removed from the vetted types. - nat gets many changes too, similarly to flow-filter. We propagate the use of the new types, in particular when building the setupu, for the three NAT modes and for the tests. We also get rid of the calls to collapse_prefixes_peering() for NAT. Note that the "exposes" field in the original type VpcManifest changes to "pub(crate)", to prevent consumers from calling it inadvertently. If these original expose blocks are necessary, the raw_exposes() methods provides access. This commit calls GwConfig::validated() to keep the old validate() method working for code that has not been migrated yet (mgmt tests, router internals). A follow-up commit will retire the old method and rename "validated()" to "validate()". Signed-off-by: Quentin Monnet <qmo@qmon.net>
Now that all consumers go through the "validated" types, we can do some clean-up: - Remove the VpcExpose accessors that have moved to its validated counterpart and that nothing else needs anymore. Mark relevant methods as "pub(crate)" only. - Similarly, we drop methods that have moved from VpcManifest to its validated counterpart and are no longer necessary. - Function collapse_prefixes_peering() is also unused and removed, while collapse_prefixes() is made "pub(crate)" only. - Remove unused Display fragments for VpcManifest. Finalise the GwConfig validation API: rename "validated(self)" to "validate(self)" and remove the older validate() method. Also add a unit test to lock in the assumption that a blank GwConfig always passes validation (used by ValidatedGwConfig::blank()). Signed-off-by: Quentin Monnet <qmo@qmon.net>
Tighten the visibility of three helpers that have no external callers left. These were originally "pub" so external consumers (mgmt, nat) could drive peering collection by hand, in tests; that need is gone now that validation owns the lifecycle. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Use validated expose objects, and the chain of parent validated objects, when building the routing configuration. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Rather than a transparent wrapper, use a dedicated struct for ValidatedManifest and copy over the exposes from the initial VpcManifest to build it. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Rather than a transparent wrapper, use a dedicated struct for ValidatedPeering. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Rather than a transparent wrapper, use a dedicated struct for ValidatedVpc. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Rather than a transparent wrapper, use a dedicated struct for ValidatedOverlay and ValidatedVpcTable. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Rather than a transparent wrapper, use a dedicated struct for ValidatedExternalConfig Signed-off-by: Quentin Monnet <qmo@qmon.net>
Rather than a transparent wrapper, use a dedicated struct for ValidatedGwConfig Signed-off-by: Quentin Monnet <qmo@qmon.net>
c6367d6 to
5217624
Compare
|
@daniel-noland @mvachhar We're now using separate structs for the validated types, see the last 7 commits. The previous commits are unchanged (only rebased on Please take another look when you can. |
This is a PR to introduce "validated" types for
VpcExpose,VpcManifest,Peering,Vpc,VpcTable,Overlay,ExternalConfig, andGwConfigobjects (all cascading from the validated expose blocks).There are two main objectives: one is to provide "vetted types" holding true some invariants, so that the compiler can reason based on that. We don't leverage this aspect that much in the PR, but the split of types between the pre-validation and post-validation stage should help for using more assumptions in the future.
The second objectif is to move some of the processing for the expose blocks to an earlier location, and make the processed prefixes available everywhere down the line. More specifically, we used to call three times the helper for "collapsing" exclusion prefixes into the allowed prefixes: once for the stateless NAT, once for the masquerading, and one for the flow-filter context builds. After the changes, we only run it once, and can expose a list of "validated" expose blocks to every location that needs it, after that.
The Pull Request is large, because the changes propagate, a lot. I think I did a reasonable job at splitting the first commits; but the last part is horrible, because there's no easy way to go incrementally with the changes once we start to propagate the new types to the consumers. So we've got some huuuuuge commits... and no real way to make it easy to review, sorry.
The code is mine, I've used Claude to rebase it (multiple times) on the recent NAT changes, as well as to assist, to some extent, to the commit split.
Feedback welcome on the new "validated" wrapper types.