-
Notifications
You must be signed in to change notification settings - Fork 201
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
cli:set: update only specific origin-hint if given (LP: #1997467) #299
Conversation
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.
See the only inline comment for details as to why this fix doesn't work :-(
netplan/cli/commands/set.py
Outdated
if self.origin_hint: # update only this specific file | ||
path = os.path.join(self.root_dir, 'etc', 'netplan', filename) | ||
if os.path.isfile(path): | ||
parser.load_yaml(path) | ||
else: | ||
parser.load_yaml_hierarchy(self.root_dir) |
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.
issue: this won't work in the general case
The only reason this works now is because you have standalone definitions. If your new definition refers to an interface defined in another file, netplan set
will fail.
A quickfix would be to add a field to netdefs (in the private_data
field) to mark them as "touched" when parsing a file without name (npp->current.file_path == NULL
), and dump them out in write_yaml_file
(or add another variant of this function with these semantics).
The problem with this quickfix is that you'll get the entire netdef dumped, not just the patched parts. A more involved fix would be to change the dirtiness tracker to store origin file rather than a simple boolean. This would allow for fine-grained control over which field we write out, but the implementation could get tricky (or very memory-hungry)
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.
Thank you very much for your review and hints. I need to allocate some extra time to dive deeper into this and fix it properly!
81a8e72
to
ecbc356
Compare
I think the problem described in LP#1997467 should be fixed now. In addition to fixing/improving the path handling (comparison/matching of "invalid" strings, starting with This PR still needs some refinement/cleanup/comments/proper commit messages and maybe handling of additional edge cases, like the global |
I'm not sure I fully grasp the new approach, but I have a first question in mind: What happens if I have this file: # read-only file
network:
ethernets:
eth0:
dhcp4: true and I do |
5ba1b28
to
c4b317b
Compare
I think this example has a flaw.. we're generally expecting Assuming that the "read-only file" pasted above is not the same as Basically, this |
4276f96
to
d28b2ce
Compare
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.
You're almost there, but there are still holes in your solution.
There's a way to paper over said holes by parsing twice the data, once for validation and once for rendering, but that's only a short-term kludge. The underlying issue is the baked-in assumption that netdef is defined only in one place, whereas even individual pieces of data can have several different origins. Fixing the root cause would take much more efforts though, so let's fix the issue at hand first :)
cc2ec7c
to
f895608
Compare
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.
LGTM to me, I just have the usual couple of stray thoughts and remarks but nothing blocking, feel free to ignore them and merge :)
Make sure we're using proper paths values (i.e. NOT "//etc/netplan/...") as we're doing string comparisons on those values and the leading double-slash might confuse those checks. E.g. in netplan.c:netplan_state_write_yaml_file() we compare netdef->filepath to the filepath of a new YAML file to be written. If a netdef has been parsed but its filepath is using double slashes, it won't match the new file and the new file will be deleted instead of updated. Starting with an empty Netplan config in /etc/netplan: $ netplan set --origin-hint test "bridges.br54.dhcp4=false" => will create test.yaml, containing the br54 definition $ netplan set --origin-hint test "bridges.br54.dhcp4=true" => updating the same br54 netdef (re-using 'netplan set') will delete test.yaml
When parsing the full YAML hierarchy (from 'netplan set'), new settings might not end up in the "to_write" GList (and therefore not in the new origin-hint file), as the same interface might have been defined in another, previous file (e.g. 0-snapd-defaults.yaml) and therefore any updates are redirected to that previous file, or ignored if a origin-hint is passed, which does not match the previous filename (e.g. origin-hint: 90-snapd-config.yaml). Still we need to parse the full YAML hierarchy for validation reasons, as a new origin-hint file might describe new definitions of an interface that contains references to a netdef from another YAML file. E.g.: $ cat 0-snapd-defaults.yaml network: ethernets: eth0: dhcp4: true $ netplan set --origin-hint=90-snapd-config "bridges.br54.interfaces=[eth0]" => This would fail if we wouldn't parse the full YAML hierarchy, before writing the 90-snapd-config.yaml file, as it would not be aware of the "eth0" netdef.
…nored) Add a new API function to allow loading nullable origin-hint overrides, i.e. all global values (like "renderer", ...) or Netdef-IDs that are part of a given YAML patch, and are supposed to be overridden inside the yaml hierarchy by the resulting origin_hint/output file. Overrides (depending on YAML hierarchy) can only happen on global values or on the individual netdef level. The following example shows how this would be represented in the datastructure: $ netplan set --origin-hint=hint "ethernets.eth0={dhcp4: false, dhcp6: NULL}" Origin-hint filename: hint.yaml YAML patch: ``` network: ethernets: eth0: dhcp4: false dchp6: NULL ``` The "null_fields" hashmap would contain one unconstrained entry for "eth0", i.e. a delete operation: network.ethernets.eth0.dhcp4 => NULL (via load_nullable_fields) The "null_overrides" hashmap would contain one entry for the "eth0" Netdef, contstrained by the origin-hint (i.e. to be ignored only if parsed from files other than "hint.yaml"): network.ethernets.eth0 => hint.yaml (via load_nullable_overrides)
When using the new libnetplan YAML parser, we need to ignore any global values (like "renderer") or netdefs (that are part of a given YAML patch) when parsing the existing YAML hierarchy, unless they're about to be parsed from an existing origin-hint file of the same name. That is to avoid assigning updates to those stanzas/netdefs to existing network definitions inside other files (not the origin-hint), as this would lead to those updates being ignored when we're writing out just the single origin-hint file (using state.write_yaml_file()). We still need to parse and validate the full context of other netdefs (not part of a given YAML patch), as the patch might reference existing netdefs (e.g. as bridge interfaces), or change current configuration in a way that makes it invalid (e.g. changing vrf tables), therefore they need to exist in the (validation-)parser's context. Also we need to parse such netdefs from an origin-hint file of same name, as we'd want update the definition in that file (instead of overriding it). We do this by adding relevant global values and netdefs, which are part of the YAML patch, to the "null_overrides" data, before parsing the full YAML hierarchy. Adding the filename of the origin-hint as a constraint, which can be checked when parsing to not ignore relevant netdefs from an exsisting origin-hint file.
When parsing the global renderer, we also want to store the path+filename of the parsed value in a new ->global_renderer datastructure (in parser & state). This way, when generating YAML output, we can put a different/new renderer stanza (e.g. from "netplan set network.renderer=...") into a higher order YAML file while conserving the renderer data in the original YAML files. The renderer can be parsed from an existing file or a new, unnamed YAML patch. If the path is known, we want to write back the data to that file only. If the path is unknown/anonymous (i.e. emtpy string) we want to write that data to the fallback file (e.g. 70-netplan-set.yaml or <origin-hint>.yaml).
f895608
to
6594416
Compare
Thank you for all the input. CI is passing and I verified the reproducers from LP#1997467 are passing (using PPA build: https://launchpad.net/~slyon/+archive/ubuntu/netplan-set-lp1997467) Merging. |
netplan.io (0.105-0ubuntu2~22.04.3) jammy; urgency=medium * Fix and improvements for the DBus integration (LP: #1997467) Cherry-picked from upstream: canonical/netplan#331 - d/p/lp1997467/0009-dbus-Build-the-copy-path-correctly.patch Properly build the destination path before copying files in the dbus integration and improve error handling - d/p/lp1997467/0010-tests-Add-an-integration-test-for-netplan-dbus.patch Add an integration test to exercise the code path where the issue was addressed. netplan.io (0.105-0ubuntu2~22.04.2) jammy; urgency=medium * d/p/lp1997467: set only specific origin-hint if given (LP: #1997467) Cherry-picked from upstream: canonical/netplan#299 - d/libnetplan0.symbols: Add netplan_parser_load_nullable_overrides() API - d/p/0008-src-parse-plug-memory-leaks-in-nullable-handling.patch backport upstream commit 40c53bb (memory leak fixup of PR#299)
…27584) The "unlink" part at the bottom of netplan.c:netplan_state_update_yaml_hierarchy seems to ignore any global values (such as "renderer") and operates on netdefs only. The issue is happens due to a combination of the following PRs: canonical#254 canonical#299 Which got SRUed into Jammy via 0.105-0ubuntu2~22.04.2 Here's a minimal reproducer: ``` netplan set network.renderer=NetworkManager --origin-hint=00-no-netdefs-just-globals netplan set network.ethernets.eth99.dhcp4=true --origin-hint=90-some-netdefs ls -l /etc/netplan/ netplan set network.ethernets.eth99=NULL cat /etc/netplan/00-no-netdefs-just-globals.yaml ``` FR-4793
…27584) The "unlink" part at the bottom of netplan.c:netplan_state_update_yaml_hierarchy seems to ignore any global values (such as "renderer") and operates on files containing netdefs only. The issue is happens due to a combination of the following PRs: canonical#254 canonical#299 Which got SRUed into Jammy via 0.105-0ubuntu2~22.04.2 Here's a minimal reproducer: ``` netplan set network.renderer=NetworkManager --origin-hint=00-no-netdefs-just-globals netplan set network.ethernets.eth99.dhcp4=true --origin-hint=90-some-netdefs ls -l /etc/netplan/ netplan set network.ethernets.eth99=NULL cat /etc/netplan/00-no-netdefs-just-globals.yaml ``` FR-4793
…27584) The "unlink" part at the bottom of netplan.c:netplan_state_update_yaml_hierarchy seems to ignore any global values (such as "renderer") and operates on files containing netdefs only. The issue is happens due to a combination of the following PRs: canonical#254 canonical#299 Which got SRUed into Jammy via 0.105-0ubuntu2~22.04.2 Here's a minimal reproducer: ``` netplan set network.renderer=NetworkManager --origin-hint=00-no-netdefs-just-globals netplan set network.ethernets.eth99.dhcp4=true --origin-hint=90-some-netdefs ls -l /etc/netplan/ netplan set network.ethernets.eth99=NULL cat /etc/netplan/00-no-netdefs-just-globals.yaml ``` FR-4793
…27584) The "unlink" part at the bottom of netplan.c:netplan_state_update_yaml_hierarchy seems to ignore any global values (such as "renderer") and operates on files containing netdefs only. The issue is happens due to a combination of the following PRs: canonical#254 canonical#299 Which got SRUed into Jammy via 0.105-0ubuntu2~22.04.2 Here's a minimal reproducer: ``` netplan set network.renderer=NetworkManager --origin-hint=00-no-netdefs-just-globals netplan set network.ethernets.eth99.dhcp4=true --origin-hint=90-some-netdefs ls -l /etc/netplan/ netplan set network.ethernets.eth99=NULL cat /etc/netplan/00-no-netdefs-just-globals.yaml ``` FR-4793
…27584) The "unlink" part at the bottom of netplan.c:netplan_state_update_yaml_hierarchy seems to ignore any global values (such as "renderer") and operates on files containing netdefs only. The issue is happens due to a combination of the following PRs: #254 #299 Which got SRUed into Jammy via 0.105-0ubuntu2~22.04.2 Here's a minimal reproducer: ``` netplan set network.renderer=NetworkManager --origin-hint=00-no-netdefs-just-globals netplan set network.ethernets.eth99.dhcp4=true --origin-hint=90-some-netdefs ls -l /etc/netplan/ netplan set network.ethernets.eth99=NULL cat /etc/netplan/00-no-netdefs-just-globals.yaml ``` FR-4793
Description
This PR solves multiple issues in
netplan set
. First, deletion of origin-hint files on update (i.e. if the netdef is already contained inside the origin-hint file). This happens when repeatedly calling netplan set on the same origin-hint and netdef, e.g.:This is fixed by the first commit:
Next, when parsing the full YAML hierarchy, stanza updates might be siphoned away, as the same interface might have been defined in another file (e.g. 0-snapd-defaults.yaml) and therefore any updates are redirected to that previous file, or ignored if an origin-hint is passed, which does not match the previous filename (e.g. origin-hint: 90-snapd-config.yaml). Still we always need to parse the full YAML hierarchy in order to validate our data. For example, somebody might call
netplan set --origin-hint=test "ethernets.br54.interfaces=[eth0]"
, which will end up having the newbr54
data inhint.yaml
but still relies on a definition ofeth0
in some other YAML file. If somebody callsnetplan set --origin-hint=test "ethernets.eth0.dhcp4=true"
OTOH, we want to ignore any previous definition ofeth0
and write our update intohint.yaml
(assuming it will be a higher order file, compared to the existing YAML hierarchy).We implemented a
load_nullable_overrides()
function, to ignore global values or netdefs that are supposed to be written into an origin-hint file.Last, we want global values (like "renderer") to be conserved per file, instead of written to any new file. When writing a new file (e.g. origin-hint.yaml) we want any
renderer
value inside the YAML hierarchy to stay unchanged. Also, we only want to add arenderer
stanza to the new file if that was given via a "netplan set ..." YAML patch (or an already existing origin-hint file already contains this stanza). So we need to track the global values on a per YAML file base and confirm the filename/path before writing those.Checklist
make check
successfully.make check-coverage
).