github.com/openshift/installer@v1.4.17/pkg/infrastructure/openstack/infraready/floatingips.go (about)

     1  package infraready
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/gophercloud/gophercloud/v2"
     8  	"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/attributestags"
     9  	"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/floatingips"
    10  	"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports"
    11  	capo "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
    12  
    13  	configv1 "github.com/openshift/api/config/v1"
    14  	"github.com/openshift/installer/pkg/asset/installconfig"
    15  	openstackdefaults "github.com/openshift/installer/pkg/types/openstack/defaults"
    16  )
    17  
    18  // FloatingIPs creates or gets the API and Ingress ports and attaches the Floating IPs to them.
    19  func FloatingIPs(ctx context.Context, cluster *capo.OpenStackCluster, installConfig *installconfig.InstallConfig, infraID string) error {
    20  	platformOpenstack := installConfig.Config.OpenStack
    21  	if lb := platformOpenstack.LoadBalancer; lb != nil && lb.Type == configv1.LoadBalancerTypeUserManaged {
    22  		return nil
    23  	}
    24  	networkClient, err := openstackdefaults.NewServiceClient(ctx, "network", openstackdefaults.DefaultClientOpts(installConfig.Config.Platform.OpenStack.Cloud))
    25  	if err != nil {
    26  		return err
    27  	}
    28  	var apiPort, ingressPort *ports.Port
    29  	if platformOpenstack.ControlPlanePort != nil && len(platformOpenstack.ControlPlanePort.FixedIPs) == 2 {
    30  		// To avoid unnecessary calls to Neutron, let's fetch the Ports in case there is a need to attach FIPs
    31  		if platformOpenstack.APIFloatingIP != "" {
    32  			// Using the first VIP as both API VIPs must be allocated on the same Port
    33  			apiPort, err = getPort(ctx, networkClient, cluster.Status.Network.ID, platformOpenstack.APIVIPs[0])
    34  			if err != nil {
    35  				return err
    36  			}
    37  		}
    38  		if platformOpenstack.IngressFloatingIP != "" {
    39  			// Using the first VIP as both Ingress VIPs must be allocated on the same Port
    40  			ingressPort, err = getPort(ctx, networkClient, cluster.Status.Network.ID, platformOpenstack.IngressVIPs[0])
    41  			if err != nil {
    42  				return err
    43  			}
    44  		}
    45  	} else {
    46  		apiPort, err = createPort(ctx, networkClient, "api", infraID, cluster.Status.Network.ID, cluster.Status.Network.Subnets[0].ID, platformOpenstack.APIVIPs[0])
    47  		if err != nil {
    48  			return err
    49  		}
    50  		ingressPort, err = createPort(ctx, networkClient, "ingress", infraID, cluster.Status.Network.ID, cluster.Status.Network.Subnets[0].ID, platformOpenstack.IngressVIPs[0])
    51  		if err != nil {
    52  			return err
    53  		}
    54  	}
    55  
    56  	if platformOpenstack.APIFloatingIP != "" {
    57  		if err := assignFIP(ctx, networkClient, platformOpenstack.APIFloatingIP, apiPort); err != nil {
    58  			return err
    59  		}
    60  	}
    61  
    62  	if platformOpenstack.IngressFloatingIP != "" {
    63  		if err := assignFIP(ctx, networkClient, platformOpenstack.IngressFloatingIP, ingressPort); err != nil {
    64  			return err
    65  		}
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // getPort retrieves a Neutron Port based on a network ID and the Fixed IP.
    72  func getPort(ctx context.Context, client *gophercloud.ServiceClient, networkID, fixedIP string) (*ports.Port, error) {
    73  	listOpts := ports.ListOpts{
    74  		NetworkID: networkID,
    75  		FixedIPs: []ports.FixedIPOpts{
    76  			{
    77  				IPAddress: fixedIP,
    78  			}},
    79  	}
    80  	allPages, err := ports.List(client, listOpts).AllPages(ctx)
    81  	if err != nil {
    82  		return nil, fmt.Errorf("failed to list Ports: %w", err)
    83  	}
    84  	allPorts, err := ports.ExtractPorts(allPages)
    85  	if err != nil {
    86  		return nil, fmt.Errorf("failed to extract Ports: %w", err)
    87  	}
    88  	if len(allPorts) != 1 {
    89  		return nil, fmt.Errorf("could not find Port with IP: %s", fixedIP)
    90  	}
    91  	return &allPorts[0], nil
    92  }
    93  
    94  func createPort(ctx context.Context, client *gophercloud.ServiceClient, role, infraID, networkID, subnetID, fixedIP string) (*ports.Port, error) {
    95  	createOpts := ports.CreateOpts{
    96  		Name:        fmt.Sprintf("%s-%s-port", infraID, role),
    97  		NetworkID:   networkID,
    98  		Description: "Created By OpenShift Installer",
    99  		FixedIPs: []ports.IP{
   100  			{
   101  				IPAddress: fixedIP,
   102  				SubnetID:  subnetID,
   103  			}},
   104  	}
   105  
   106  	port, err := ports.Create(ctx, client, createOpts).Extract()
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	tag := fmt.Sprintf("openshiftClusterID=%s", infraID)
   112  	err = attributestags.Add(ctx, client, "ports", port.ID, tag).ExtractErr()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return port, err
   117  }
   118  
   119  func assignFIP(ctx context.Context, client *gophercloud.ServiceClient, address string, port *ports.Port) error {
   120  	listOpts := floatingips.ListOpts{
   121  		FloatingIP: address,
   122  	}
   123  	allPages, err := floatingips.List(client, listOpts).AllPages(ctx)
   124  	if err != nil {
   125  		return fmt.Errorf("failed to list floating IPs: %w", err)
   126  	}
   127  	allFIPs, err := floatingips.ExtractFloatingIPs(allPages)
   128  	if err != nil {
   129  		return fmt.Errorf("failed to extract floating IPs: %w", err)
   130  	}
   131  
   132  	if len(allFIPs) != 1 {
   133  		return fmt.Errorf("could not find FIP: %s", address)
   134  	}
   135  
   136  	fip := allFIPs[0]
   137  
   138  	updateOpts := floatingips.UpdateOpts{
   139  		PortID: &port.ID,
   140  	}
   141  
   142  	_, err = floatingips.Update(ctx, client, fip.ID, updateOpts).Extract()
   143  	if err != nil {
   144  		return fmt.Errorf("failed to attach floating IP to port: %w", err)
   145  	}
   146  	return nil
   147  }