-
Notifications
You must be signed in to change notification settings - Fork 263
OpenStack single-stack IPv6 support #1909
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
base: main
Are you sure you want to change the base?
Conversation
9e7cba8 to
87e4a48
Compare
|
Not sure that will work. What happens if the instance is IPv6 only? Do requests fail differently than if the network is not up yet? |
|
One of the principle behind Ignition is that it will retry indefinitely until the server explicitly returns an error. |
87e4a48 to
5fad1fd
Compare
I updated my code logic. In an IPv6-only instance, IPv4 will fail because it's not found and the resources will be returned from IPv6. If both fails it will return errors. The error will be handled with a message in dispatch function above. It makes sense? One thing I need to consider is the possibility of having both IPv4 and IPv6 simultaneously. Is that possible? If yes, I need to handle this case as well. |
Nice. I guess this is being handled in Thanks! |
Yes, this is very common. |
5fad1fd to
91f89b5
Compare
I updated the code to cover this scenarios. So, now the code looks like this:
These should be the basic scenarios. Now, I'm working on running my code in the OpenStack environment and reading the necessary docs to test it correctly. Thanks for reviewing and helping me with this task! |
91f89b5 to
a850f29
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.
Something to take into consideration:
| } | ||
| ipv6MetadataServiceUrl = url.URL{ | ||
| Scheme: "http", | ||
| Host: "[fe80::a9fe:a9fe]", |
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.
I wonder if we might need to specify the interface name like it's mentioned on the docs
https://docs.openstack.org/nova/latest/user/metadata.html#the-metadata-service
and how it's done elsewhere
https://github.com/canonical/cloud-init/blob/main/cloudinit/sources/DataSourceOpenStack.py#L76
d81e5fb to
6b8a11e
Compare
| } | ||
| ipv6MetadataServiceUrl = url.URL{ | ||
| Scheme: "http", | ||
| Host: fmt.Sprintf("[fe80::a9fe:a9fe%%%s]", url.PathEscape(iface)), |
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.
Maybe we need to change to [fe80::a9fe:a9fe%25%s] refer to https://docs.openstack.org/nova/latest/user/metadata.html#the-metadata-service
f2979b8 to
fa50682
Compare
fa50682 to
ee0d2af
Compare
e105dea to
1569e4c
Compare
1569e4c to
35c1993
Compare
| } | ||
| metadataServiceUrlIPv6 = url.URL{ | ||
| Scheme: "http", | ||
| Host: "fe80::a9fe:a9fe%", |
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.
Have you tried having the host be [fe90::a9fe:a9fe%]? since you are getting an error on fetch due to improper escaping ?
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.
|
Hey team, I'm experiencing an error while trying to connect the server. I added some log to the code, then I ran Not sure why is not parsing correctly. |
|
[SOLVED] Mobbing session output: We utilized an OpenStack environment to fetch and curl the IPv6, but were unable to get a result. Here are the logs: [EDIT] I was able to reproduce this commands in other machine and worked. It might be a older openstack version, since IPV6 support only came after Victoria release. |
787e7b9 to
0bb5233
Compare
18f03e7 to
b3d311d
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.
Really good start, and thank you for working on this, just a few questions and nits.
| isIPv6 := ip.To4() == nil | ||
| return isIPv6 | ||
| } | ||
|
|
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.
I think it would be good to have some reference on why you are looking for the zone ID in the manner that you are. A simple link to the IPV6 metadata service docs should be good with a quick summary? wdyt?
| fmt.Println("Fetching zone id...") | ||
| interfaces, err := net.Interfaces() | ||
| if err != nil { | ||
| return "", fmt.Errorf("error fetching zone id: %v", err) |
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.
I think it might be more appropriate to say network interfaces as this is not really the zone id, and could confuse debugging. wdyt?
"error fetching network interfaces: %v", err
| continue | ||
| } | ||
|
|
||
| for _, addr := range addrs { |
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.
Can I get a small comment explaining how this is giving us the zoneID?
dbb2567 to
e6b084a
Compare
|
|
||
| // the metadata server exists but doesn't contain any actual metadata, | ||
| // assume that there is no config specified | ||
| if err == resource.ErrNotFound { |
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.
shouldn't we continue handling the not found error like it was done here?
I believe the same would need to happen for IPv6.
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.
Thanks for working on this and for the initial effort. This is less trivial than expected :D
Here's some feedback:
- The whole implementation (ipv4 / ipv6 detection) should be common to all providers. E.g: there is currently a similar ask for Scaleway provider (#1897 (comment))
- For the implementation itself: it might increase the provisioning time as we need to wait for IPv4 to be seen as "unreachable" before giving up and trying IPv6 (not to mention that sometimes the interface itself is not directly available to the instance)
ignition/internal/resource/http.go
Line 309 in b111af1
duration := initialBackoff
Something I would propose:
- For a cloud provider, we define a map of URLs:
urls := map[IP]url.URL{
IPv4: url.URL{
Scheme: "http",
Host: "169.254.169.254",
Path: "openstack/latest/user_data",
},
IPv6: url.URL{
Scheme: "http",
Host: "[...]",
Path: "openstack/latest/user_data",
}
}- In a common implementation, we start two go routines sharing a
successchannel. One routine trying the IPv4 stack and the other one using the IPv6 stack (with the correct IP for each) - of course if the initial URLs map contains only IPv4, we start only the IPv4 function (same goes for IPv6). - The first routine to return a
success(i.e an Ignition configuration) will notify the channel and we can continue as usual (similar to what @prestist suggested here: https://github.com/coreos/ignition/pull/1909/files#r1985704411)
I'm not a big fan of using Go routines, but I think this situation might be a good usecase.
| return nil, nil | ||
| // Try fetching from IPv4 first | ||
| response, err = f.FetchToBuffer(metadataServiceUrlIPv4, resource.FetchOptions{}) | ||
| f.Logger.Debug("IPv6 URL:", metadataServiceUrlIPv6.Host) |
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.
| f.Logger.Debug("IPv6 URL:", metadataServiceUrlIPv6.Host) | |
| f.Logger.Debug("IPv4 URL:", metadataServiceUrlIPv6.Host) |
We're trying to fetch IPv4 first?
e6b084a to
50a4776
Compare
Signed-off-by: Mathieu Tortuyaux <[email protected]>
This defines a wrapper that will try in paralell both IPv4 and IPv6 when the provider declares those two IPs. Signed-off-by: Mathieu Tortuyaux <[email protected]>
|
Hey @tormath1, thanks for reviewing and working on that! I went through your changes, and your approach makes complete sense. I agree with your comments. To align with your work, I’ve cherry-picked the changes you made in internal/resource/url.go, and I’ll adapt the provider side accordingly so we can implement something like what you did for Scaleway for OpenStack! Thanks again! |
|
@tormath1 I just finished implementing the helper! I'd really appreciate it if you could take a look and share your feedback. |
Thanks for the ping! I somehow missed it. I'll give a try to this implementation, I noticed some hiccups in the current approach but I think it should be easy to fix. I'll let you know :) |
This defines a wrapper that will try in paralell both IPv4 and IPv6 when the provider declares those two IPs. Signed-off-by: Mathieu Tortuyaux <[email protected]>
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.
I think this is super close, I thiiink the main breakage is the template we have for ipv6, but I think how we reach out and find our nic is a point of failure as well. If we do my comments I think this will work :)
|
|
||
| resource.IPv6: { | ||
| Scheme: "http", | ||
| Host: "[fe80::a9fe:a9fe%iface]", |
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.
I think we are missing a bit of the ipv6
[fe80::a9fe:a9fe%25iface]
| return data, nil | ||
| } | ||
|
|
||
| func findInterfaceWithIPv6() (string, error) { |
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.
So I think it might be good to try all interfaces rather then just the first one we find. So lets return all IPV6 interfaces which are UP and not loopback. i.e return ( []string,error)
| string(resource.IPv4): userdataURLs[resource.IPv4], | ||
| } | ||
|
|
||
| ifaceName, err := findInterfaceWithIPv6() |
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.
Now since we have a list of strings, rather then 1 lets reach out to all of them at the same time.
I think we can do this a few ways, though I think it would be cool to do it in parallel using fan-out pattern.
Golang has a few concepts that will help us to achieve that.
…attern using goroutines and waitgroups


In #1897 issue, we are requested to create support for IPv6 so Ignition can fetch the metadata in single-stack environments.
To achieve this, we added a new URL with the IPv6 endpoint. We also created logic to first attempt the IPv4 endpoint so If this fails, it will try the IPv6 one. If both endpoints fail, it will return the appropriate error.