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 }