Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ Request Body:

Member subnet ID of the load balancer created.

- `loadbalancer.openstack.org/node-address-type`

The node address type to use when selecting the IP address for load balancer pool members. By default, InternalIP is preferred over ExternalIP. Set to `ExternalIP` to reverse this priority, which is useful when nodes have multiple interfaces and the member subnet corresponds to the ExternalIP network.

Default: InternalIP is tried first, then ExternalIP.

Possible values: `ExternalIP`

- `loadbalancer.openstack.org/network-id`

The network ID which will allocate virtual IP for loadbalancer.
Expand Down
18 changes: 14 additions & 4 deletions pkg/openstack/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const (
ServiceAnnotationLoadBalancerSubnetID = "loadbalancer.openstack.org/subnet-id"
ServiceAnnotationLoadBalancerNetworkID = "loadbalancer.openstack.org/network-id"
ServiceAnnotationLoadBalancerMemberSubnetID = "loadbalancer.openstack.org/member-subnet-id"
ServiceAnnotationLoadBalancerNodeAddressType = "loadbalancer.openstack.org/node-address-type"
ServiceAnnotationLoadBalancerTimeoutClientData = "loadbalancer.openstack.org/timeout-client-data"
ServiceAnnotationLoadBalancerTimeoutMemberConnect = "loadbalancer.openstack.org/timeout-member-connect"
ServiceAnnotationLoadBalancerTimeoutMemberData = "loadbalancer.openstack.org/timeout-member-data"
Expand Down Expand Up @@ -145,7 +146,8 @@ type serviceConfig struct {
healthMonitorTimeout int
healthMonitorMaxRetries int
healthMonitorMaxRetriesDown int
preferredIPFamily corev1.IPFamily // preferred (the first) IP family indicated in service's `spec.ipFamilies`
preferredIPFamily corev1.IPFamily // preferred (the first) IP family indicated in service's `spec.ipFamilies`
nodeAddressType corev1.NodeAddressType // preferred node address type for pool members
}

type listenerKey struct {
Expand Down Expand Up @@ -382,13 +384,17 @@ func (lbaas *LbaasV2) getLoadBalancerLegacyName(service *corev1.Service) string
// If neither InternalIP nor ExternalIP can be found an error is
// returned.
// If preferredIPFamily is specified, only address of the specified IP family can be returned.
func nodeAddressForLB(node *corev1.Node, preferredIPFamily corev1.IPFamily) (string, error) {
// If preferredAddrType is ExternalIP, ExternalIP is tried first.
func nodeAddressForLB(node *corev1.Node, preferredIPFamily corev1.IPFamily, preferredAddrType corev1.NodeAddressType) (string, error) {
addrs := node.Status.Addresses
if len(addrs) == 0 {
return "", cpoerrors.ErrNoAddressFound
}

allowedAddrTypes := []corev1.NodeAddressType{corev1.NodeInternalIP, corev1.NodeExternalIP}
if preferredAddrType == corev1.NodeExternalIP {
allowedAddrTypes = []corev1.NodeAddressType{corev1.NodeExternalIP, corev1.NodeInternalIP}
}
for _, allowedAddrType := range allowedAddrTypes {
for _, addr := range addrs {
if addr.Type == allowedAddrType {
Expand Down Expand Up @@ -493,7 +499,7 @@ func getProxyProtocolFromServiceAnnotation(service *corev1.Service) *v2pools.Pro

// getSubnetIDForLB returns subnet-id for a specific node
func getSubnetIDForLB(ctx context.Context, network *gophercloud.ServiceClient, node corev1.Node, preferredIPFamily corev1.IPFamily) (string, error) {
ipAddress, err := nodeAddressForLB(&node, preferredIPFamily)
ipAddress, err := nodeAddressForLB(&node, preferredIPFamily, "")
if err != nil {
return "", err
}
Expand Down Expand Up @@ -1017,7 +1023,7 @@ func (lbaas *LbaasV2) buildBatchUpdateMemberOpts(ctx context.Context, port corev
newMembers := sets.New[string]()

for _, node := range nodes {
addr, err := nodeAddressForLB(node, svcConf.preferredIPFamily)
addr, err := nodeAddressForLB(node, svcConf.preferredIPFamily, svcConf.nodeAddressType)
if err != nil {
if err == cpoerrors.ErrNoAddressFound {
// Node failure, do not create member
Expand Down Expand Up @@ -1308,6 +1314,8 @@ func (lbaas *LbaasV2) checkServiceUpdate(ctx context.Context, service *corev1.Se
svcConf.preferredIPFamily = service.Spec.IPFamilies[0]
}

svcConf.nodeAddressType = corev1.NodeAddressType(getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerNodeAddressType, ""))

// Find subnet ID for creating members
memberSubnetID, err := lbaas.getMemberSubnetID(service)
if err != nil {
Expand Down Expand Up @@ -1371,6 +1379,8 @@ func (lbaas *LbaasV2) checkService(ctx context.Context, service *corev1.Service,
svcConf.preferredIPFamily = service.Spec.IPFamilies[0]
}

svcConf.nodeAddressType = corev1.NodeAddressType(getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerNodeAddressType, ""))

// If in the config file internal-lb=true, user is not allowed to create external service.
if lbaas.opts.InternalLB {
if !getBoolFromServiceAnnotation(service, ServiceAnnotationLoadBalancerInternal, false) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/openstack/loadbalancer_sg.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func applyNodeSecurityGroupIDForLB(ctx context.Context, network *gophercloud.Ser
return fmt.Errorf("error getting server ID from the node: %w", err)
}

addr, _ := nodeAddressForLB(node, svcConf.preferredIPFamily)
addr, _ := nodeAddressForLB(node, svcConf.preferredIPFamily, svcConf.nodeAddressType)
if addr == "" {
// If node has no viable address let's ignore it.
continue
Expand Down
68 changes: 67 additions & 1 deletion pkg/openstack/loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,7 @@ func Test_nodeAddressForLB(t *testing.T) {
type testArgs struct {
node *corev1.Node
preferredIPFamily corev1.IPFamily
preferredAddrType corev1.NodeAddressType
}

tests := []struct {
Expand Down Expand Up @@ -1775,11 +1776,76 @@ func Test_nodeAddressForLB(t *testing.T) {
expect: "",
expectedErr: cpoerrors.ErrNoAddressFound,
},
{
name: "ExternalIP preferred over InternalIP with IPv4",
testArgs: testArgs{
node: &corev1.Node{
Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{
{
Type: corev1.NodeInternalIP,
Address: "192.168.1.1",
},
{
Type: corev1.NodeExternalIP,
Address: "10.0.0.1",
},
},
},
},
preferredIPFamily: corev1.IPv4Protocol,
preferredAddrType: corev1.NodeExternalIP,
},
expect: "10.0.0.1",
expectedErr: nil,
},
{
name: "ExternalIP preferred falls back to InternalIP when no ExternalIP",
testArgs: testArgs{
node: &corev1.Node{
Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{
{
Type: corev1.NodeInternalIP,
Address: "192.168.1.1",
},
},
},
},
preferredIPFamily: corev1.IPv4Protocol,
preferredAddrType: corev1.NodeExternalIP,
},
expect: "192.168.1.1",
expectedErr: nil,
},
{
name: "ExternalIP preferred with IPv6",
testArgs: testArgs{
node: &corev1.Node{
Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{
{
Type: corev1.NodeInternalIP,
Address: "2001:db8::1",
},
{
Type: corev1.NodeExternalIP,
Address: "2001:db8::2",
},
},
},
},
preferredIPFamily: corev1.IPv6Protocol,
preferredAddrType: corev1.NodeExternalIP,
},
expect: "2001:db8::2",
expectedErr: nil,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := nodeAddressForLB(test.testArgs.node, test.testArgs.preferredIPFamily)
got, err := nodeAddressForLB(test.testArgs.node, test.testArgs.preferredIPFamily, test.testArgs.preferredAddrType)
if test.expectedErr != nil {
assert.EqualError(t, err, test.expectedErr.Error())
} else {
Expand Down