Change:
LBaaS implementation (v1 or v2) will not support services that require to load balancer more than one port.
Fixed for LBaaS v2 by I suspect LBaaS v1 can't support multiple ports (not sure if reusing the VIP across multiple loadbalancers is a universally supported thing). I also believe LBaaS v1 is also mostly gone in favour of v2 in deployments so trying to address this in v1 is extremely low priority unless someone steps forward to ask for it. So: I think we can close this bug now, but I lack the powers to do that directly.
thank you for the update.

Discussion:
LoadBalancer } <ins> type empty struct{} </ins> func networkExtensions(client *gophercloud.ServiceClient) (map[string]bool, error) { seen := make(map[string]bool)

return &loadbalancerList[0], nil } <del> func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) error { </del> <ins> func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) (string, error) { </ins> start := time.Now().Second() for { loadbalancer, err := loadbalancers.Get(client, loadbalancerID).Extract() if err != nil { <del> return err </del> <ins> return "", err </ins> } if loadbalancer.ProvisioningStatus == "ACTIVE" { <del> return nil </del> <ins> return "ACTIVE", nil } else if loadbalancer.ProvisioningStatus == "ERROR" { return "ERROR", fmt.Errorf("Loadbalancer has gone into ERROR state") </ins> } time.Sleep(1 * time.Second) if time.Now().Second()-start >= loadbalancerActiveTimeoutSeconds { <del> return fmt.Errorf("Loadbalancer failed to go into ACTIVE provisioning status within alloted time") </del> <ins> return loadbalancer.ProvisioningStatus, fmt.Errorf("Loadbalancer failed to go into ACTIVE provisioning status within alloted time") </ins> } } }

glog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", clusterName, apiService.Namespace, apiService.Name, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, hosts, apiService.Annotations) ports := apiService.Spec.Ports <del> if len(ports) > 1 { return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers") } else if len(ports) == 0 { </del> <ins> if len(ports) == 0 { </ins> return nil, fmt.Errorf("no ports provided to openstack load balancer") } <del> // The service controller verified all the protocols match on the ports, just check and use the first one </del> <ins> // Check for TCP protocol on each port </ins> // TODO: Convert all error messages to use an event recorder <del> if ports[0].Protocol != api.ProtocolTCP { return nil, fmt.Errorf("Only TCP LoadBalancer is supported for openstack load balancers") </del> <ins> for _, port := range ports { if port.Protocol != api.ProtocolTCP { return nil, fmt.Errorf("Only TCP LoadBalancer is supported for openstack load balancers") } </ins> } affinity := api.ServiceAffinityNone //apiService.Spec.SessionAffinity

waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) <del> listener, err := listeners.Create(lbaas.network, listeners.CreateOpts{ Name:           name, Protocol:       listeners.ProtocolTCP, ProtocolPort:   (int)(ports[0].Port), //TODO: need to handle multi-port LoadbalancerID: loadbalancer.ID, }).Extract() if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) pool, err := v2_pools.Create(lbaas.network, v2_pools.CreateOpts{ Name:        name, Protocol:    v2_pools.ProtocolTCP, LBMethod:    lbmethod, ListenerID:  listener.ID, Persistence: persistence, }).Extract() if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err } for _, host := range hosts { addr, err := getAddressByName(lbaas.compute, host) </del> <ins> for portIndex, port := range ports { listener, err := listeners.Create(lbaas.network, listeners.CreateOpts{ Name:           fmt.Sprintf("listener_%s_%d", name, portIndex), Protocol:       listeners.Protocol(port.Protocol), ProtocolPort:   int(port.Port), LoadbalancerID: loadbalancer.ID, }).Extract() </ins> if err != nil { <ins> // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) </ins> return nil, err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) <del> _, err = v2_pools.CreateAssociateMember(lbaas.network, pool.ID, v2_pools.MemberCreateOpts{ ProtocolPort: int(ports[0].NodePort), //TODO: need to handle multi-port Address:      addr, SubnetID:     lbaas.opts.SubnetId, </del> <ins> pool, err := v2_pools.Create(lbaas.network, v2_pools.CreateOpts{ Name:        fmt.Sprintf("pool_%s_%d", name, portIndex), Protocol:    v2_pools.Protocol(port.Protocol), LBMethod:    lbmethod, ListenerID:  listener.ID, Persistence: persistence, </ins> }).Extract() if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err } <del> } if lbaas.opts.CreateMonitor { </del> waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) <del> _, err = v2_monitors.Create(lbaas.network, v2_monitors.CreateOpts{ PoolID:     pool.ID, Type:       monitors.TypeTCP, Delay:      int(lbaas.opts.MonitorDelay.Duration.Seconds()), Timeout:    int(lbaas.opts.MonitorTimeout.Duration.Seconds()), MaxRetries: int(lbaas.opts.MonitorMaxRetries), }).Extract() if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err </del> <ins> for _, host := range hosts { addr, err := getAddressByName(lbaas.compute, host) if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err } _, err = v2_pools.CreateAssociateMember(lbaas.network, pool.ID, v2_pools.MemberCreateOpts{ ProtocolPort: int(port.NodePort), Address:      addr, SubnetID:     lbaas.opts.SubnetId, }).Extract() if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } if lbaas.opts.CreateMonitor { _, err = v2_monitors.Create(lbaas.network, v2_monitors.CreateOpts{ PoolID:     pool.ID, Type:       string(port.Protocol), Delay:      int(lbaas.opts.MonitorDelay.Duration.Seconds()), Timeout:    int(lbaas.opts.MonitorTimeout.Duration.Seconds()), MaxRetries: int(lbaas.opts.MonitorMaxRetries), }).Extract() if err != nil { // cleanup what was created so far _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return nil, err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) </ins> } }

glog.V(4).Infof("UpdateLoadBalancer(%v, %v, %v)", clusterName, loadBalancerName, hosts) ports := service.Spec.Ports <del> if len(ports) > 1 { return fmt.Errorf("multiple ports are not yet supported in openstack load balancers") } else if len(ports) == 0 { </del> <ins> if len(ports) == 0 { </ins> return fmt.Errorf("no ports provided to openstack load balancer") }

return fmt.Errorf("Loadbalancer %s does not exist", loadBalancerName) } <del> // Set of member (addresses) that _should_ exist addrs := map[string]bool{} for _, host := range hosts { addr, err := getAddressByName(lbaas.compute, host) </del> <ins> // Get all listeners for this loadbalancer, by "port key". type portKey struct { Protocol string Port     int } lbListeners := make(map[portKey]listeners.Listener) err = listeners.List(lbaas.network, listeners.ListOpts{LoadbalancerID: loadbalancer.ID}).EachPage(func(page pagination.Page) (bool, error) { listenersList, err := listeners.ExtractListeners(page) </ins> if err != nil { <del> return err </del> <ins> return false, err </ins> } <del> addrs[addr] = true </del> <ins> for _, l := range listenersList { key := portKey{Protocol: l.Protocol, Port: l.ProtocolPort} lbListeners[key] = l } return true, nil }) if err != nil { return err </ins> } <del> // Iterate over members in each pool that _do_ exist var poolID string </del> <ins> // Get all pools for this loadbalancer, by listener ID. lbPools := make(map[string]v2_pools.Pool) </ins> err = v2_pools.List(lbaas.network, v2_pools.ListOpts{LoadbalancerID: loadbalancer.ID}).EachPage(func(page pagination.Page) (bool, error) { poolsList, err := v2_pools.ExtractPools(page) if err != nil { return false, err } <del> for _, pool := range poolsList { poolID = pool.ID err := v2_pools.ListAssociateMembers(lbaas.network, poolID, v2_pools.MemberListOpts{}).EachPage(func(page pagination.Page) (bool, error) { membersList, err := v2_pools.ExtractMembers(page) if err != nil { return false, err } for _, member := range membersList { if _, found := addrs[member.Address]; found { // Member already exists, remove from update list delete(addrs, member.Address) } else { // Member needs to be deleted err = v2_pools.DeleteMember(lbaas.network, poolID, member.ID).ExtractErr() if err != nil { return false, err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } } return true, nil }) if err != nil { return false, err </del> <ins> for _, p := range poolsList { for _, l := range p.Listeners { lbPools[l.ID] = p </ins> } } return true, nil

return err } <del> // Anything left in addrs is a new member that needs to be added to a pool for addr := range addrs { _, err := v2_pools.CreateAssociateMember(lbaas.network, poolID, v2_pools.MemberCreateOpts{ Address:      addr, ProtocolPort: int(ports[0].NodePort), SubnetID:     lbaas.opts.SubnetId, }).Extract() </del> <ins> // Compose Set of member (addresses) that _should_ exist addrs := map[string]empty{} for _, host := range hosts { addr, err := getAddressByName(lbaas.compute, host) </ins> if err != nil { return err } <del> waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) </del> <ins> addrs[addr] = empty{} </ins> } <ins> // Check for adding/removing members associated with each port for _, port := range ports { // Get listener associated with this port listener, ok := lbListeners[portKey{ Protocol: string(port.Protocol), Port:     int(port.Port), }] if !ok { return fmt.Errorf("Loadbalancer %s does not contain required listener for port %d and protocol %s", loadBalancerName, port.Port, port.Protocol) } // Get pool associated with this listener pool, ok := lbPools[listener.ID] if !ok { return fmt.Errorf("Loadbalancer %s does not contain required pool for listener %s", loadBalancerName, listener.ID) } // Find existing pool members (by address) for this port members := make(map[string]v2_pools.Member) err := v2_pools.ListAssociateMembers(lbaas.network, pool.ID, v2_pools.MemberListOpts{}).EachPage(func(page pagination.Page) (bool, error) { membersList, err := v2_pools.ExtractMembers(page) if err != nil { return false, err } for _, member := range membersList { members[member.Address] = member } return true, nil }) if err != nil { return err } // Add any new members for this port for addr := range addrs { if _, ok := members[addr]; ok { // Already exists, do not create member continue } _, err := v2_pools.CreateAssociateMember(lbaas.network, pool.ID, v2_pools.MemberCreateOpts{ Address:      addr, ProtocolPort: int(port.NodePort), SubnetID:     lbaas.opts.SubnetId, }).Extract() if err != nil { return err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } // Remove any old members for this port for _, member := range members { if _, ok := addrs[member.Address]; ok { // Still present, do not delete member continue } err = v2_pools.DeleteMember(lbaas.network, pool.ID, member.ID).ExtractErr() if err != nil && !isNotFound(err) { return err } waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } } </ins> return nil }

ports := apiService.Spec.Ports if len(ports) > 1 { <del> return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers") </del> <ins> return nil, fmt.Errorf("multiple ports are not supported in openstack v1 load balancers") </ins> } else if len(ports) == 0 { return nil, fmt.Errorf("no ports provided to openstack load balancer") }

_, err = members.Create(lb.network, members.CreateOpts{ PoolID:       pool.ID, <del> ProtocolPort: int(ports[0].NodePort), //TODO: need to handle multi-port </del> <ins> ProtocolPort: int(ports[0].NodePort), //Note: only handles single port </ins> Address:      addr, }).Extract() if err != nil {