Only restore a configured MAC addr on restart.#47233
Only restore a configured MAC addr on restart.#47233akerouanton merged 2 commits intomoby:masterfrom
Conversation
d20f9f3 to
ac4388b
Compare
api/types/network/endpoint.go
Outdated
| // API param field) when the container is created, and cleared in the inspect | ||
| // output so that only MacAddress is returned to clients. DesiredMacAddress is | ||
| // used to set the value for netlabel.MacAddress passed to the driver. | ||
| DesiredMacAddress string `json:",omitempty"` |
There was a problem hiding this comment.
I'd even not expose it through the API, otherwise we'll have to support it until the API version v1.44 is dropped:
| DesiredMacAddress string `json:",omitempty"` | |
| DesiredMacAddress string `json:"-"` |
There was a problem hiding this comment.
That'd stop it being encoded for persistent storage... so its value wouldn't be preserved over a daemon restart.
So, the idea is to deal with that by including it in the JSON when it has a value - but always clear it in the copy used for 'inspect' responses.
There was a problem hiding this comment.
Huh, right. I totally forgot we're using the same json tag for both HTTP and storage concerns.
There was a problem hiding this comment.
Why couldn't this field be added to EndpointSettings?
// EndpointSettings is a package local wrapper for
// networktypes.EndpointSettings which stores Endpoint state that
// needs to be persisted to disk but not exposed in the api.There was a problem hiding this comment.
I'd forgotten / not-noticed that the comment says those extra fields are persisted - but I don't think they are?
I could be wrong but, I think, the wrapper is always reconstructed here when the container's started ...
moby/daemon/container_operations.go
Lines 758 to 762 in 794f712
Also, the local-wrapper fields aren't available to the daemon's 'inspect' code - so it couldn't differentiate between a generated address and a configured one when it needed to fill in the container-wide Config.MacAddress.
(In the first PR, I got the logic wrong for deciding whether the address was configured - it wasn't as similar to the operIPAM case as we thought, so operMAC wasn't set up properly. And because the field isn't persisted, I don't think it was possible to tell the difference between a generated MAC and a configured one during the container-run, it has to be spotted and remembered during the create.)
If that seems right, I'll fix the comment.
There was a problem hiding this comment.
I'd forgotten / not-noticed that the comment says those extra fields are persisted - but I don't think they are?
They're persisted in the container config: (container.Container).NetworkSettings[].Networks[].
I could be wrong but, I think, the wrapper is always reconstructed here when the container's started ...
So? That could be changed.
Also, the local-wrapper fields aren't available to the daemon's 'inspect' code - so it couldn't differentiate between a generated address and a configured one when it needed to fill in the container-wide
Config.MacAddress.
The local-wrapper fields are accessible from the *Container. I find it hard to believe that they are not available to the code which maps an ID to a *Container and then to an API response.
There was a problem hiding this comment.
Ok, thank you - that helped me to untangle it a bit.
api/types/network/endpoint.go
Outdated
| IPv6Gateway string | ||
| GlobalIPv6Address string | ||
| GlobalIPv6PrefixLen int | ||
| MacAddress string |
There was a problem hiding this comment.
No need to move that field, it's still what should be used by API clients to configure a container's MAC address.
There was a problem hiding this comment.
My thinking was that after the value from the API has been copied into DesiredMacAddress, the MacAddress field does become operational data - it's reset in cleanOperationalData(), along with the rest.
But yes, if this struct might be used by someone trying to figure out what to put in the API request, I can move it back. (It's bound to be a bit confusing until we properly separate configured state from running state.)
51ce31b to
9e387e5
Compare
54c8faa to
aa20cae
Compare
c113c51 to
98a0d0d
Compare
corhere
left a comment
There was a problem hiding this comment.
Everything not commented on LGTM
| // Check that a configured MAC address is restored after a container restart, | ||
| // and after a daemon restart. | ||
| func TestCfgdMACAddrOnRestart(t *testing.T) { | ||
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") |
There was a problem hiding this comment.
Are the changes applicable to Windows containers? How feasible would it be to add Windows integration tests?
There was a problem hiding this comment.
The issue with a MAC address generated by the bridge n/w driver getting reused after a container restart, then becoming a duplicate, doesn't apply on native-Windows - there's no bridge driver. (And these tests are all bridge-network based.)
Do we have any networking integration tests that start containers on Windows? There's probably a gap we should try to fill.
But, I think it has to be out of scope for this bug fix?
There was a problem hiding this comment.
The tests use the bridge driver, but are the daemon code paths being touched specific to bridge, or Linux? Because it looks suspiciously like the code is cross-platform.
moby/libnetwork/drivers/windows/windows.go
Lines 539 to 545 in cff4f20
There was a problem hiding this comment.
There's no way to repro the Linux duplicate MAC address problem, because Windows MAC address generation doesn't work like that (addresses seem to be random, not based on IPAM-assigned IP addresses).
But I have checked that, on Windows without this change, a configured MAC address was not preserved over a daemon restart. With the change, it is.
It should be possible to write an automated regression test for that, and probably not too difficult - but I'd rather deal with it separately ... it's going to take me some time to figure out how to run an integration test on local Windows, and/or a number of pushes to GitHub to get it right.
So, perhaps we can resolve this if I raise an issue saying the regression test is needed, and assign it to myself to have a go at?
There was a problem hiding this comment.
So, perhaps we can resolve this if I raise an issue saying the regression test is needed, and assign it to myself to have a go at?
I'd be satisfied with editing the "how to verify" section of the PR description to mention that you manually tested the change on Windows. Filing an issue to add a Windows regression test would be a huge bonus.
There was a problem hiding this comment.
Good stuff... that's done.
98a0d0d to
4ace85d
Compare
The API's EndpointConfig struct has a MacAddress field that's used for both the configured address, and the current address (which may be generated). A configured address must be restored when a container is restarted, but a generated address must not. The previous attempt to differentiate between the two, without adding a field to the API's EndpointConfig that would show up in 'inspect' output, was a field in the daemon's version of EndpointSettings, MACOperational. It did not work, MACOperational was set to true when a configured address was used. So, while it ensured addresses were regenerated, it failed to preserve a configured address. So, this change removes that code, and adds DesiredMacAddress to the wrapped version of EndpointSettings, where it is persisted but does not appear in 'inspect' results. Its value is copied from MacAddress (the API field) when a container is created. Signed-off-by: Rob Murray <rob.murray@docker.com>
Do not set 'Config.MacAddress' in inspect output unless the MAC address is configured. Also, make sure it is filled in for a configured address on the default network before the container is started (by translating the network name from 'default' to 'config' so that the address lookup works). Signed-off-by: Rob Murray <rob.murray@docker.com>
4ace85d to
8c64b85
Compare
- What I did
The API's
EndpointConfigstruct has aMacAddressfield that's used for both the configured address, and the current address (which may be generated).A configured address must be restored when a container is restarted, but a generated address must not.
The previous attempt to differentiate between the two, without adding a field to the API's
EndpointConfigthat would show up in 'inspect' output, was a field in the daemon's version ofEndpointSettings,MACOperational. It did not work,MACOperationalwas set to 'true' when a configured address was used. So, it ensured addresses were regenerated, but failed to preserve a configured address.Also, only fill in
Config.MacAddressin 'inspect' output when the MAC address is configured.Fixes #47228
Fixes #47146
- How I did it
So, this change removes code from #47168, and instead adds
DesiredMacAddressto the wrapped version of EndpointSettings, which are persisted but do not form part of the 'inspect' output.Its value is copied from MacAddress (the API field) when a container is created.
The second git commit fills in
Config.MacAddressin inspect output usingDesiredMacAddress, which is empty when the address is generated. It also ensures the field is filled in for a configured address on the default bridge network, before the container is started, by translating the network namedefaultintobridge.- How to verify it
New tests added.
On Windows - run a container like this ...
docker run -ti --mac-address 00-11-22-33-44-55 mcr.microsoft.com/windows/nanoserver:ltsc2022 cmd.exe... and use
ipconfig /allto check that the configured MAC address has been used. Also checkinspectshows the correct address.Stop the container, restart the daemon, and restart the container. Then, exec into the container, check that the configured MAC address is still in-use - and that the inspect output is still correct.
This should be automated - #47283
- Description for the changelog
Ensure that a generated MAC address is not restored when a container is restarted, but a configured MAC address is preserved.
Containers created using 25.0.0 may have duplicate MAC addresses, they must be re-created.
Containers created using 25.0.0 or 25.0.1 with user-defined MAC addresses will get generated MAC addresses when they are started using 25.0.2. They must also be re-created.