-
Notifications
You must be signed in to change notification settings - Fork 62
Add a diff visitor for BlueprintDatasetsConfig
#7366
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
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99e2f84 to
b9b9ec0
Compare
Contributor
Author
|
I'm sure you're starting to see a pattern here @jgallagher. This one is nearly identical in structure to the last two. |
jgallagher
approved these changes
Jan 17, 2025
andrewjstone
added a commit
that referenced
this pull request
Jan 25, 2025
This commit introduces an automated blueprint diff mechanism based on diffus along with the visitor pattern for traversing heterogeneous diff trees. It builds on several prior commits that introduced diffus support and added visitors for types: #7261 #7336 #7362 #7366 The new `BlueprintDiffer` implements the relevant visitors and accumulates change state from visitor method callbacks. The accumulated state is the `BlueprintDiff` which replaces the old `BlueprintDiff`. The new structure intentionally groups resources by sleds, as the 4 primary maps in a `Blueprint` (`sled_state`, `blueprint_zones`, `blueprint_disks`, `blueprint_datasets`) are going to be collapsed into a single map. This allows us to modify the visitors to traverse the new blueprint diffs in one place and not have to worry about modifying the `BlueprintDiff` itself or any consumers. More details about why we are collapsing these maps can be found in #7078. We primarily use `BlueprintDiff`s for testing and omdb output, and therefore the printed representation is absolutely critical for us. This commit reuses the types and formatting contained in `nexus/ types/src/deployment/ blueprint_display.rs` and provides a new type, `BpDiffPrintable` that can be created from a `BlueprintDiff`. `BpDiffPrintable` contains our formatted tables ready to be used by `BlueprintDiffDisplay` along with the orignal blueprints to render the diff. It's possible that we can collapse `BlueprintDiffDisplay` and `BpDiffPrintable` into one type and simplify a bit. I just haven't done that yet. The printable output of the diffs has maintained backwards compatibility in all cases except for errors and warnings. Those have been specifically adapted to work with our visitors and be accumulated while walking the diffus diffs. This only resulted in the change to one test output file, which should leave us confident in the correctness of this new implementation. Usage going forward -------------------- This is a significant change to our blueprint diff code, and in some cases it may take slightly more boilerplate in order to diff newly added fields or structs, than in the older code. However, there are a few things this new style has going for it. Figuring out the differences between blueprints is now done automatically and completely. There is no need to compute these differences, which is both error prone, and complicated. See the old [BpDiffZones](https:// github.com/ oxidecomputer/omicron/blob/888f90db8b36ccdf667d96423ae7805824c48aa9/ nexus/types/src/deployment/blueprint_diff.rs#L195-L340) code for what finding the differences for just zones in a blueprint looked like. With automated diffs output from diffus, we now need to write code to expose the necessary change information to consumers. We want to do this in a unified and rigorous manner, and one that composes nicely. We chose a `visitor` pattern for this. For each somewhat complex type (use your judgement), we provide a visitor trait that walks the heterogeneous diff tree and calls trait methods when a change is found. User code implements only the visitor methods that it cares about and then can accumulate its own internal state based on which callbacks fire. We provide a top-level `VisitBlueprint` trait to detect all changes in a blueprint and implement it in `BlueprintDiffer` to construct a `BlueprintDiff` in a format we can use. There are a few important things to note about our visitors. While diffus provides a complete tree of all changes as well as fields and variants that have not changed, the visitors as currently implemented only expose changes. These changes are unified for simplicity into a `Change` type. We also don't currently expose all changes. For most fields that we don't expect to ever change, there is not a change callback. Mostly this is because the visitors were written before I had to use them :). It turns out it may actually useful to expose these fields so that we can report errors or warnings in our diffs if they do change unexpectedly. You can see an example of error tracking in the `BlueprintDiffer::visit_zone_type_change` method and its test output in `nexus/reconfigurator/planning/tests/output/ planner_nonprovisionable_2_2a.txt`. It is tedious to write visitor traits to walk a diffus diff and then implement these traits to accumulate the state we want. It can reasonably be asked why we wouldn't just walk the tree and generate the state we want directly without the visitors. Indeed, this is quite reasonable for small one off tasks. But we are building a foundation that will grow over time in terms of type structure and number of developers working on the system. For such a long term project, it's usefulf to decouple the tree walking from the state accumulation. For one thing, it makes each bit easier to read and write on its own. For another, it allows us to build different implementations of the visitors for different use cases, such as testing. Remember, users only have to implement the methods they care about. Therefore if they only want to see if a zone changed, they don't have to worry about parsing diffus types, but can just implement a callback. An example of this is the `TestVisitor` in `live-tests/tests/test_nexus_add_remove.rs`. I anticipate we will find ways to make implementing and using diff visitors more ergonomic over time while maintaining the compositional rigor that the separation of concerns provides in this model. Testing -------- I mainly have run planning tests locally to ensure diffs work as expected. I still need to run the live tests and play with omdb.
andrewjstone
added a commit
that referenced
this pull request
Jan 25, 2025
This commit introduces an automated blueprint diff mechanism based on [diffus](https://github.com/oxidecomputer/diffus) along with the visitor pattern for traversing heterogeneous diff trees. It builds on several prior commits that introduced diffus support and added visitors for types: #7261 #7336 #7362 #7366 The new `BlueprintDiffer` implements the relevant visitors and accumulates change state from visitor method callbacks. The accumulated state is the `BlueprintDiff` which replaces the old `BlueprintDiff`. The new structure intentionally groups resources by sleds, as the 4 primary maps in a `Blueprint` (`sled_state`, `blueprint_zones`, `blueprint_disks`, `blueprint_datasets`) are going to be collapsed into a single map. This allows us to modify the visitors to traverse the new blueprint diffs in one place and not have to worry about modifying the `BlueprintDiff` itself or any consumers. More details about why we are collapsing these maps can be found in #7078. We primarily use `BlueprintDiff`s for testing and omdb output, and therefore the printed representation is absolutely critical for us. This commit reuses the types and formatting contained in `nexus/ types/src/deployment/ blueprint_display.rs` and provides a new type, `BpDiffPrintable` that can be created from a `BlueprintDiff`. `BpDiffPrintable` contains our formatted tables ready to be used by `BlueprintDiffDisplay` along with the orignal blueprints to render the diff. It's possible that we can collapse `BlueprintDiffDisplay` and `BpDiffPrintable` into one type and simplify a bit. I just haven't done that yet. The printable output of the diffs has maintained backwards compatibility in all cases except for errors and warnings. Those have been specifically adapted to work with our visitors and be accumulated while walking the diffus diffs. This only resulted in the change to one test output file, which should leave us confident in the correctness of this new implementation. Usage going forward -------------------- This is a significant change to our blueprint diff code, and in some cases it may take slightly more boilerplate in order to diff newly added fields or structs, than in the older code. However, there are a few things this new style has going for it. Figuring out the differences between blueprints is now done automatically and completely. There is no need to compute these differences, which is both error prone, and complicated. See the old [BpDiffZones](https:// github.com/oxidecomputer/omicron/blob/888f90db8b36ccdf667d96423ae7805824c48aa9/nexus/types/src/deployment/blueprint_diff.rs#L195-L340) code for what finding the differences for just zones in a blueprint looked like. With automated diffs output from diffus, we now need to write code to expose the necessary change information to consumers. We want to do this in a unified and rigorous manner, and one that composes nicely. We chose a `visitor` pattern for this. For each somewhat complex type (use your judgement), we provide a visitor trait that walks the heterogeneous diff tree and calls trait methods when a change is found. User code implements only the visitor methods that it cares about and then can accumulate its own internal state based on which callbacks fire. We provide a top-level `VisitBlueprint` trait to detect all changes in a blueprint and implement it in `BlueprintDiffer` to construct a `BlueprintDiff` in a format we can use. There are a few important things to note about our visitors. While diffus provides a complete tree of all changes as well as fields and variants that have not changed, the visitors as currently implemented only expose changes. These changes are unified for simplicity into a `Change` type. We also don't currently expose all changes. For most fields that we don't expect to ever change, there is not a change callback. Mostly this is because the visitors were written before I had to use them :). It turns out it may actually useful to expose these fields so that we can report errors or warnings in our diffs if they do change unexpectedly. You can see an example of error tracking in the `BlueprintDiffer::visit_zone_type_change` method and its test output in `nexus/reconfigurator/planning/tests/output/ planner_nonprovisionable_2_2a.txt`. It is tedious to write visitor traits to walk a diffus diff and then implement these traits to accumulate the state we want. It can reasonably be asked why we wouldn't just walk the tree and generate the state we want directly without the visitors. Indeed, this is quite reasonable for small one off tasks. But we are building a foundation that will grow over time in terms of type structure and number of developers working on the system. For such a long term project, it's usefulf to decouple the tree walking from the state accumulation. For one thing, it makes each bit easier to read and write on its own. For another, it allows us to build different implementations of the visitors for different use cases, such as testing. Remember, users only have to implement the methods they care about. Therefore if they only want to see if a zone changed, they don't have to worry about parsing diffus types, but can just implement a callback. An example of this is the `TestVisitor` in `live-tests/tests/test_nexus_add_remove.rs`. I anticipate we will find ways to make implementing and using diff visitors more ergonomic over time while maintaining the compositional rigor that the separation of concerns provides in this model. Testing -------- I mainly have run planning tests locally to ensure diffs work as expected. I still need to run the live tests and play with omdb.
andrewjstone
added a commit
that referenced
this pull request
Jan 25, 2025
This commit introduces an automated blueprint diff mechanism based on [diffus](https://github.com/oxidecomputer/diffus) along with the visitor pattern for traversing heterogeneous diff trees. It builds on several prior commits that introduced diffus support and added visitors for types: #7261 #7336 #7362 #7366 The new `BlueprintDiffer` implements the relevant visitors and accumulates change state from visitor method callbacks. The accumulated state is the `BlueprintDiff` which replaces the old `BlueprintDiff`. The new structure intentionally groups resources by sleds, as the 4 primary maps in a `Blueprint` (`sled_state`, `blueprint_zones`, `blueprint_disks`, `blueprint_datasets`) are going to be collapsed into a single map. This allows us to modify the visitors to traverse the new blueprint diffs in one place and not have to worry about modifying the `BlueprintDiff` itself or any consumers. More details about why we are collapsing these maps can be found in #7078. We primarily use `BlueprintDiff`s for testing and omdb output, and therefore the printed representation is absolutely critical for us. This commit reuses the types and formatting contained in `nexus/ types/src/deployment/ blueprint_display.rs` and provides a new type, `BpDiffPrintable` that can be created from a `BlueprintDiff`. `BpDiffPrintable` contains our formatted tables ready to be used by `BlueprintDiffDisplay` along with the orignal blueprints to render the diff. It's possible that we can collapse `BlueprintDiffDisplay` and `BpDiffPrintable` into one type and simplify a bit. I just haven't done that yet. The printable output of the diffs has maintained backwards compatibility in all cases except for errors and warnings. Those have been specifically adapted to work with our visitors and be accumulated while walking the diffus diffs. This only resulted in the change to one test output file, which should leave us confident in the correctness of this new implementation. An optional `show_unchanged` flag was added at key points in the code and in the future we plan to change the default to only show actual changes. We didn't do that here so that we could ensure the output of the new implementation matches the existing code. We also plan to add more columns, such as `disposition` for datasets. Usage going forward -------------------- This is a significant change to our blueprint diff code, and in some cases it may take slightly more boilerplate in order to diff newly added fields or structs, than in the older code. However, there are a few things this new style has going for it. Figuring out the differences between blueprints is now done automatically and completely. There is no need to compute these differences, which is both error prone, and complicated. See the old [BpDiffZones](https:// github.com/oxidecomputer/omicron/blob/888f90db8b36ccdf667d96423ae7805824c48aa9/nexus/types/src/deployment/blueprint_diff.rs#L195-L340) code for what finding the differences for just zones in a blueprint looked like. With automated diffs output from diffus, we now need to write code to expose the necessary change information to consumers. We want to do this in a unified and rigorous manner, and one that composes nicely. We chose a `visitor` pattern for this. For each somewhat complex type (use your judgement), we provide a visitor trait that walks the heterogeneous diff tree and calls trait methods when a change is found. User code implements only the visitor methods that it cares about and then can accumulate its own internal state based on which callbacks fire. We provide a top-level `VisitBlueprint` trait to detect all changes in a blueprint and implement it in `BlueprintDiffer` to construct a `BlueprintDiff` in a format we can use. There are a few important things to note about our visitors. While diffus provides a complete tree of all changes as well as fields and variants that have not changed, the visitors as currently implemented only expose changes. These changes are unified for simplicity into a `Change` type. We also don't currently expose all changes. For most fields that we don't expect to ever change, there is not a change callback. Mostly this is because the visitors were written before I had to use them :). It turns out it may actually useful to expose these fields so that we can report errors or warnings in our diffs if they do change unexpectedly. You can see an example of error tracking in the `BlueprintDiffer::visit_zone_type_change` method and its test output in `nexus/reconfigurator/planning/tests/output/ planner_nonprovisionable_2_2a.txt`. It is tedious to write visitor traits to walk a diffus diff and then implement these traits to accumulate the state we want. It can reasonably be asked why we wouldn't just walk the tree and generate the state we want directly without the visitors. Indeed, this is quite reasonable for small one off tasks. But we are building a foundation that will grow over time in terms of type structure and number of developers working on the system. For such a long term project, it's usefulf to decouple the tree walking from the state accumulation. For one thing, it makes each bit easier to read and write on its own. For another, it allows us to build different implementations of the visitors for different use cases, such as testing. Remember, users only have to implement the methods they care about. Therefore if they only want to see if a zone changed, they don't have to worry about parsing diffus types, but can just implement a callback. An example of this is the `TestVisitor` in `live-tests/tests/test_nexus_add_remove.rs`. I anticipate we will find ways to make implementing and using diff visitors more ergonomic over time while maintaining the compositional rigor that the separation of concerns provides in this model. Testing -------- I mainly have run planning tests locally to ensure diffs work as expected. I still need to run the live tests and play with omdb.
andrewjstone
added a commit
that referenced
this pull request
Jan 25, 2025
This commit introduces an automated blueprint diff mechanism based on [diffus](https://github.com/oxidecomputer/diffus) along with the visitor pattern for traversing heterogeneous diff trees. It builds on several prior commits that introduced diffus support and added visitors for types: #7261 #7336 #7362 #7366 The new `BlueprintDiffer` implements the relevant visitors and accumulates change state from visitor method callbacks. The accumulated state is the `BlueprintDiff` which replaces the old `BlueprintDiff`. The new structure intentionally groups resources by sleds, as the 4 primary maps in a `Blueprint` (`sled_state`, `blueprint_zones`, `blueprint_disks`, `blueprint_datasets`) are going to be collapsed into a single map. This allows us to modify the visitors to traverse the new blueprint diffs in one place and not have to worry about modifying the `BlueprintDiff` itself or any consumers. More details about why we are collapsing these maps can be found in #7078. We primarily use `BlueprintDiff`s for testing and omdb output, and therefore the printed representation is absolutely critical for us. This commit reuses the types and formatting contained in `nexus/ types/src/deployment/ blueprint_display.rs` and provides a new type, `BpDiffPrintable` that can be created from a `BlueprintDiff`. `BpDiffPrintable` contains our formatted tables ready to be used by `BlueprintDiffDisplay` along with the orignal blueprints to render the diff. It's possible that we can collapse `BlueprintDiffDisplay` and `BpDiffPrintable` into one type and simplify a bit. I just haven't done that yet. The printable output of the diffs has maintained backwards compatibility in all cases except for errors and warnings. Those have been specifically adapted to work with our visitors and be accumulated while walking the diffus diffs. This only resulted in the change to one test output file, which should leave us confident in the correctness of this new implementation. An optional `show_unchanged` flag was added at key points in the code and in the future we plan to change the default to only show actual changes. We didn't do that here so that we could ensure the output of the new implementation matches the existing code. We also plan to add more columns, such as `disposition` for datasets. Usage going forward -------------------- This is a significant change to our blueprint diff code, and in some cases it may take slightly more boilerplate in order to diff newly added fields or structs, than in the older code. However, there are a few things this new style has going for it. Figuring out the differences between blueprints is now done automatically and completely. There is no need to compute these differences, which is both error prone, and complicated. See the old [BpDiffZones](https:// github.com/oxidecomputer/omicron/blob/888f90db8b36ccdf667d96423ae7805824c48aa9/nexus/types/src/deployment/blueprint_diff.rs#L195-L340) code for what finding the differences for just zones in a blueprint looked like. With automated diffs output from diffus, we now need to write code to expose the necessary change information to consumers. We want to do this in a unified and rigorous manner, and one that composes nicely. We chose a `visitor` pattern for this. For each somewhat complex type (use your judgement), we provide a visitor trait that walks the heterogeneous diff tree and calls trait methods when a change is found. User code implements only the visitor methods that it cares about and then can accumulate its own internal state based on which callbacks fire. We provide a top-level `VisitBlueprint` trait to detect all changes in a blueprint and implement it in `BlueprintDiffer` to construct a `BlueprintDiff` in a format we can use. There are a few important things to note about our visitors. While diffus provides a complete tree of all changes as well as fields and variants that have not changed, the visitors as currently implemented only expose changes. These changes are unified for simplicity into a `Change` type. We also don't currently expose all changes. For most fields that we don't expect to ever change, there is not a change callback. Mostly this is because the visitors were written before I had to use them :). It turns out it may actually be useful to expose these fields so that we can report errors or warnings in our diffs if they do change unexpectedly. You can see an example of error tracking in the `BlueprintDiffer::visit_zone_type_change` method and its test output in `nexus/reconfigurator/planning/tests/output/ planner_nonprovisionable_2_2a.txt`. It is tedious to write visitor traits to walk a diffus diff and then implement these traits to accumulate the state we want. It can reasonably be asked why we wouldn't just walk the tree and generate the state we want directly without the visitors. Indeed, this is quite reasonable for small one off tasks. But we are building a foundation that will grow over time in terms of type structure and number of developers working on the system. For such a long term project, it's usefulf to decouple the tree walking from the state accumulation. For one thing, it makes each bit easier to read and write on its own. For another, it allows us to build different implementations of the visitors for different use cases, such as testing. Remember, users only have to implement the methods they care about. Therefore if they only want to see if a zone changed, they don't have to worry about parsing diffus types, but can just implement a callback. An example of this is the `TestVisitor` in `live-tests/tests/test_nexus_add_remove.rs`. I anticipate we will find ways to make implementing and using diff visitors more ergonomic over time while maintaining the compositional rigor that the separation of concerns provides in this model. Testing -------- I mainly have run planning tests locally to ensure diffs work as expected. I still need to run the live tests and play with omdb.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
No description provided.