Skip to content
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

OpenStack single-stack IPv6 support #1909

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Starting with this release, ignition-validate binaries are signed with the
- Support partitioning disk with mounted partitions
- Support Proxmox VE
- Support gzipped Akamai user_data
- Support IPv6 for single-stack OpenStack

### Changes

Expand Down
90 changes: 83 additions & 7 deletions internal/providers/openstack/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package openstack
import (
"context"
"fmt"
"net"
"net/url"
"os"
"os/exec"
Expand All @@ -44,11 +45,16 @@ const (
)

var (
metadataServiceUrl = url.URL{
metadataServiceUrlIPv4 = url.URL{
Scheme: "http",
Host: "169.254.169.254",
Path: "openstack/latest/user_data",
}
metadataServiceUrlIPv6 = url.URL{
Scheme: "http",
Host: "[fe80::a9fe:a9fe%]",
Path: "openstack/latest/user_data",
}
)

func init() {
Expand Down Expand Up @@ -166,14 +172,84 @@ func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, path string)
return os.ReadFile(filepath.Join(mnt, configDriveUserdataPath))
}

func isIPv6Address(ip net.IP) bool {
isIPv6 := ip.To4() == nil
return isIPv6
}

Copy link
Collaborator

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?

func findZoneID() (string, error) {
fmt.Println("Fetching zone id...")
interfaces, err := net.Interfaces()
if err != nil {
return "", fmt.Errorf("error fetching zone id: %v", err)
Copy link
Collaborator

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

}

for _, iface := range interfaces {
fmt.Printf("Checking interface: %s\n", iface.Name)

if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}

addrs, err := iface.Addrs()
if err != nil {
fmt.Printf("Error fetching addresses for interface %s: %v\n", iface.Name, err)
continue
}

for _, addr := range addrs {
Copy link
Collaborator

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?

if ipnet, ok := addr.(*net.IPNet); ok && isIPv6Address(ipnet.IP) {
return iface.Name, nil
}
}
}
return "", fmt.Errorf("no active IPv6 network interface found")
}

// Fetches configuration from both IPv4 and IPv6 metadata services
func fetchConfigFromMetadataService(f *resource.Fetcher) ([]byte, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this whole function is a bit confusing in the direction it wants to go. The intent is to get our config from the metadata service. If a metadata service has our config we should be happy regardless of which one had it.

Currently, we are fetching two times even if we already have the data we need to continue.
I think we can restructure this to be a little more clear.

First we need to decide based on the issue which metadata service should be primary?
Then it falls into something like

fetch from primary 
if fail fetch from secondary

wdyt?

res, err := f.FetchToBuffer(metadataServiceUrl, resource.FetchOptions{})
var ipv4Res, ipv6Res []byte
var ipv4Err, ipv6Err error

// Attempt to fetch from IPv4
ipv4Res, ipv4Err = f.FetchToBuffer(metadataServiceUrlIPv4, resource.FetchOptions{})
if ipv4Err == nil {
fmt.Println("Successfully fetched configuration from IPv4.")

// If IPv4 succeeds, attempt to fetch from IPv6 as well
interfaceName, err := findZoneID()
if err != nil {
fmt.Printf("IPv6 metadata service lookup failed: %v\n", err)
return ipv4Res, fmt.Errorf("IPv6 lookup failed, returning IPv4 result")
}
metadataServiceUrlIPv6.Host = fmt.Sprintf("[%s%s]", "fe80::a9fe:a9fe%", interfaceName)
metadataServiceUrlIPv6Str := fmt.Sprintf("http://[%s%s]/openstack/latest/user_data", metadataServiceUrlIPv6.Host, interfaceName)
fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6Str)
ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{})

if ipv6Err != nil {
fmt.Printf("IPv6 metadata service failed: %v\n", ipv6Err)
return ipv4Res, fmt.Errorf("IPv4 succeeded, but IPv6 failed: %v", ipv6Err)
}
fmt.Println("Successfully fetched configuration from both IPv4 and IPv6.")
return append(ipv4Res, ipv6Res...), nil
}

// the metadata server exists but doesn't contain any actual metadata,
// assume that there is no config specified
if err == resource.ErrNotFound {
return nil, nil
// If IPv4 fails, attempt to fetch from IPv6
interfaceName, err := findZoneID()
if err != nil {
fmt.Printf("IPv6 metadata service lookup failed: %v\n", err)
return nil, fmt.Errorf("both IPv4 and IPv6 lookup failed")
}

return res, err
metadataServiceUrlIPv6.Host = fmt.Sprintf("[%s%s]", "fe80::a9fe:a9fe%", interfaceName)
metadataServiceUrlIPv6Str := fmt.Sprintf("http://[%s%s]/openstack/latest/user_data", metadataServiceUrlIPv6.Host, interfaceName)
fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6Str)
ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{})

if ipv6Err != nil {
fmt.Printf("IPv6 metadata service failed: %v\n", ipv6Err)
return nil, fmt.Errorf("both IPv4 and IPv6 services failed")
}
return ipv6Res, fmt.Errorf("IPv4 failed, returning IPv6 result")
}
Loading