pkg/providers/utils/ingress_status.go (172 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package utils
import (
"fmt"
"net"
"sort"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
listerscorev1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
"github.com/apache/apisix-ingress-controller/pkg/log"
)
const (
_gatewayLBNotReadyMessage = "The LoadBalancer used by the APISIX gateway is not yet ready"
)
// IngressPublishAddresses get addressed used to expose Ingress
func IngressPublishAddresses(ingressPublishService string, ingressStatusAddress []string, svcLister listerscorev1.ServiceLister) ([]string, error) {
addrs := []string{}
// if ingressStatusAddress is specified, it will be used first
if len(ingressStatusAddress) > 0 {
addrs = append(addrs, ingressStatusAddress...)
return addrs, nil
}
namespace, name, err := cache.SplitMetaNamespaceKey(ingressPublishService)
if err != nil {
log.Errorf("invalid ingressPublishService %s: %s", ingressPublishService, err)
return nil, err
}
svc, err := svcLister.Services(namespace).Get(name)
if err != nil {
return nil, err
}
switch svc.Spec.Type {
case corev1.ServiceTypeLoadBalancer:
if len(svc.Status.LoadBalancer.Ingress) < 1 {
return addrs, fmt.Errorf("%s", _gatewayLBNotReadyMessage)
}
for _, ip := range svc.Status.LoadBalancer.Ingress {
if ip.IP == "" {
// typically AWS load-balancers
addrs = append(addrs, ip.Hostname)
} else {
addrs = append(addrs, ip.IP)
}
}
addrs = append(addrs, svc.Spec.ExternalIPs...)
return addrs, nil
default:
return addrs, nil
}
}
// IngressLBStatusIPs organizes the available addresses
func IngressLBStatusIPs(ingressPublishService string, ingressStatusAddress []string, svcLister listerscorev1.ServiceLister) ([]corev1.LoadBalancerIngress, error) {
lbips := []corev1.LoadBalancerIngress{}
var ips []string
for {
var err error
ips, err = IngressPublishAddresses(ingressPublishService, ingressStatusAddress, svcLister)
if err != nil {
if err.Error() == _gatewayLBNotReadyMessage {
log.Warnf("%s. Provided service: %s", _gatewayLBNotReadyMessage, ingressPublishService)
time.Sleep(time.Second)
continue
}
return nil, err
}
break
}
for _, ip := range ips {
if net.ParseIP(ip) == nil {
lbips = append(lbips, corev1.LoadBalancerIngress{Hostname: ip})
} else {
lbips = append(lbips, corev1.LoadBalancerIngress{IP: ip})
}
}
return lbips, nil
}
func lessNetworkingV1LB(addrs []networkingv1.IngressLoadBalancerIngress) func(int, int) bool {
return func(a, b int) bool {
switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) {
case -1:
return true
case 1:
return false
}
return addrs[a].IP < addrs[b].IP
}
}
func lessNetworkingV1beta1LB(addrs []networkingv1beta1.IngressLoadBalancerIngress) func(int, int) bool {
return func(a, b int) bool {
switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) {
case -1:
return true
case 1:
return false
}
return addrs[a].IP < addrs[b].IP
}
}
func CompareNetworkingV1LBEqual(lb1 []networkingv1.IngressLoadBalancerIngress, lb2 []networkingv1.IngressLoadBalancerIngress) bool {
if len(lb1) != len(lb2) {
return false
}
sort.SliceStable(lb1, lessNetworkingV1LB(lb1))
sort.SliceStable(lb2, lessNetworkingV1LB(lb2))
size := len(lb1)
for i := 0; i < size; i++ {
if lb1[i].IP != lb2[i].IP {
return false
}
if lb1[i].Hostname != lb2[i].Hostname {
return false
}
}
return true
}
func CompareNetworkingV1beta1LBEqual(lb1 []networkingv1beta1.IngressLoadBalancerIngress, lb2 []networkingv1beta1.IngressLoadBalancerIngress) bool {
if len(lb1) != len(lb2) {
return false
}
sort.SliceStable(lb1, lessNetworkingV1beta1LB(lb1))
sort.SliceStable(lb2, lessNetworkingV1beta1LB(lb2))
size := len(lb1)
for i := 0; i < size; i++ {
if lb1[i].IP != lb2[i].IP {
return false
}
if lb1[i].Hostname != lb2[i].Hostname {
return false
}
}
return true
}
// CoreV1ToNetworkV1LB convert []corev1.LoadBalancerIngress to []networkingv1.IngressLoadBalancerIngress
func CoreV1ToNetworkV1LB(lbips []corev1.LoadBalancerIngress) []networkingv1.IngressLoadBalancerIngress {
t := make([]networkingv1.IngressLoadBalancerIngress, 0, len(lbips))
for _, lbip := range lbips {
t = append(t, networkingv1.IngressLoadBalancerIngress{
Hostname: lbip.Hostname,
IP: lbip.IP,
})
}
return t
}
// CoreV1ToNetworkV1beta1LB convert []corev1.LoadBalancerIngress to []networkingv1beta1.IngressLoadBalancerIngress
func CoreV1ToNetworkV1beta1LB(lbips []corev1.LoadBalancerIngress) []networkingv1beta1.IngressLoadBalancerIngress {
t := make([]networkingv1beta1.IngressLoadBalancerIngress, 0, len(lbips))
for _, lbip := range lbips {
t = append(t, networkingv1beta1.IngressLoadBalancerIngress{
Hostname: lbip.Hostname,
IP: lbip.IP,
})
}
return t
}
func CoreV1ToGatewayV1beta1Addr(lbips []corev1.LoadBalancerIngress) []gatewayv1beta1.GatewayAddress {
t := make([]gatewayv1beta1.GatewayAddress, 0, len(lbips))
// In the definition, there is also an address type called NamedAddress,
// which we currently do not implement
HostnameAddressType := gatewayv1beta1.HostnameAddressType
IPAddressType := gatewayv1beta1.IPAddressType
for _, lbip := range lbips {
if v := lbip.Hostname; v != "" {
t = append(t, gatewayv1beta1.GatewayAddress{
Type: &HostnameAddressType,
Value: v,
})
}
if v := lbip.IP; v != "" {
t = append(t, gatewayv1beta1.GatewayAddress{
Type: &IPAddressType,
Value: v,
})
}
}
return t
}