istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/gateway/context.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gateway 16 17 import ( 18 "fmt" 19 "sort" 20 "strconv" 21 "strings" 22 23 corev1 "k8s.io/api/core/v1" 24 25 networking "istio.io/api/networking/v1alpha3" 26 "istio.io/istio/pilot/pkg/model" 27 "istio.io/istio/pkg/cluster" 28 "istio.io/istio/pkg/config/constants" 29 "istio.io/istio/pkg/config/host" 30 "istio.io/istio/pkg/util/sets" 31 ) 32 33 // GatewayContext contains a minimal subset of push context functionality to be exposed to GatewayAPIControllers 34 type GatewayContext struct { 35 ps *model.PushContext 36 cluster cluster.ID 37 } 38 39 func NewGatewayContext(ps *model.PushContext, cluster cluster.ID) GatewayContext { 40 return GatewayContext{ps, cluster} 41 } 42 43 // ResolveGatewayInstances attempts to resolve all instances that a gateway will be exposed on. 44 // Note: this function considers *all* instances of the service; its possible those instances will not actually be properly functioning 45 // gateways, so this is not 100% accurate, but sufficient to expose intent to users. 46 // The actual configuration generation is done on a per-workload basis and will get the exact set of matched instances for that workload. 47 // Four sets are exposed: 48 // * Internal addresses (eg istio-ingressgateway.istio-system.svc.cluster.local:80). 49 // * Internal IP addresses (eg 1.2.3.4). This comes from ClusterIP. 50 // * External addresses (eg 1.2.3.4), this comes from LoadBalancer services. There may be multiple in some cases (especially multi cluster). 51 // * Pending addresses (eg istio-ingressgateway.istio-system.svc), are LoadBalancer-type services with pending external addresses. 52 // * Warnings for references that could not be resolved. These are intended to be user facing. 53 func (gc GatewayContext) ResolveGatewayInstances( 54 namespace string, 55 gwsvcs []string, 56 servers []*networking.Server, 57 ) (internal, internalIP, external, pending, warns []string, allUsable bool) { 58 ports := map[int]struct{}{} 59 for _, s := range servers { 60 ports[int(s.Port.Number)] = struct{}{} 61 } 62 foundInternal := sets.New[string]() 63 foundInternalIP := sets.New[string]() 64 foundExternal := sets.New[string]() 65 foundPending := sets.New[string]() 66 warnings := []string{} 67 foundUnusable := false 68 log.Debugf("Resolving gateway instances for %v in namespace %s", gwsvcs, namespace) 69 for _, g := range gwsvcs { 70 svc, f := gc.ps.ServiceIndex.HostnameAndNamespace[host.Name(g)][namespace] 71 if !f { 72 otherNamespaces := []string{} 73 for ns := range gc.ps.ServiceIndex.HostnameAndNamespace[host.Name(g)] { 74 otherNamespaces = append(otherNamespaces, `"`+ns+`"`) // Wrap in quotes for output 75 } 76 if len(otherNamespaces) > 0 { 77 sort.Strings(otherNamespaces) 78 warnings = append(warnings, fmt.Sprintf("hostname %q not found in namespace %q, but it was found in namespace(s) %v", 79 g, namespace, strings.Join(otherNamespaces, ", "))) 80 } else { 81 warnings = append(warnings, fmt.Sprintf("hostname %q not found", g)) 82 } 83 foundUnusable = true 84 continue 85 } 86 svcKey := svc.Key() 87 for port := range ports { 88 instances := gc.ps.ServiceEndpointsByPort(svc, port, nil) 89 if len(instances) > 0 { 90 foundInternal.Insert(fmt.Sprintf("%s:%d", g, port)) 91 foundInternalIP.InsertAll(svc.GetAddresses(&model.Proxy{Metadata: &model.NodeMetadata{ClusterID: gc.cluster}})...) 92 if svc.Attributes.ClusterExternalAddresses.Len() > 0 { 93 // Fetch external IPs from all clusters 94 svc.Attributes.ClusterExternalAddresses.ForEach(func(c cluster.ID, externalIPs []string) { 95 foundExternal.InsertAll(externalIPs...) 96 }) 97 } else if corev1.ServiceType(svc.Attributes.Type) == corev1.ServiceTypeLoadBalancer { 98 if !foundPending.Contains(g) { 99 warnings = append(warnings, fmt.Sprintf("address pending for hostname %q", g)) 100 foundPending.Insert(g) 101 } 102 } 103 } else { 104 instancesByPort := gc.ps.ServiceEndpoints(svcKey) 105 if instancesEmpty(instancesByPort) { 106 warnings = append(warnings, fmt.Sprintf("no instances found for hostname %q", g)) 107 } else { 108 hintPort := sets.New[string]() 109 for servicePort, instances := range instancesByPort { 110 for _, i := range instances { 111 if i.EndpointPort == uint32(port) { 112 hintPort.Insert(strconv.Itoa(servicePort)) 113 } 114 } 115 } 116 if hintPort.Len() > 0 { 117 warnings = append(warnings, fmt.Sprintf( 118 "port %d not found for hostname %q (hint: the service port should be specified, not the workload port. Did you mean one of these ports: %v?)", 119 port, g, sets.SortedList(hintPort))) 120 foundUnusable = true 121 } else { 122 _, isManaged := svc.Attributes.Labels[constants.ManagedGatewayLabel] 123 var portExistsOnService bool 124 for _, p := range svc.Ports { 125 if p.Port == port { 126 portExistsOnService = true 127 break 128 } 129 } 130 // If this is a managed gateway, the only possible explanation for no instances for the port 131 // is a delay in endpoint sync. Therefore, we don't want to warn/change the Programmed condition 132 // in this case as long as the port exists on the `Service` object. 133 if !isManaged || !portExistsOnService { 134 warnings = append(warnings, fmt.Sprintf("port %d not found for hostname %q", port, g)) 135 foundUnusable = true 136 } 137 } 138 } 139 } 140 } 141 } 142 sort.Strings(warnings) 143 return sets.SortedList(foundInternal), sets.SortedList(foundInternalIP), sets.SortedList(foundExternal), sets.SortedList(foundPending), 144 warnings, !foundUnusable 145 } 146 147 func (gc GatewayContext) GetService(hostname, namespace string) *model.Service { 148 return gc.ps.ServiceIndex.HostnameAndNamespace[host.Name(hostname)][namespace] 149 } 150 151 func instancesEmpty(m map[int][]*model.IstioEndpoint) bool { 152 for _, instances := range m { 153 if len(instances) > 0 { 154 return false 155 } 156 } 157 return true 158 }