github.com/projectcontour/contour@v1.28.2/cmd/contour/ingressstatus.go (about) 1 // Copyright Project Contour Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package main 15 16 import ( 17 "context" 18 "net" 19 "strings" 20 21 contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" 22 "github.com/projectcontour/contour/internal/k8s" 23 "github.com/sirupsen/logrus" 24 v1 "k8s.io/api/core/v1" 25 networking_v1 "k8s.io/api/networking/v1" 26 "k8s.io/apimachinery/pkg/types" 27 "sigs.k8s.io/controller-runtime/pkg/cache" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 gatewayapi_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 30 ) 31 32 // loadBalancerStatusWriter orchestrates LoadBalancer address status 33 // updates for HTTPProxy, Ingress and Gateway objects. Actually updating the 34 // address in the object status is performed by k8s.StatusAddressUpdater. 35 // 36 // The theory of operation of the loadBalancerStatusWriter is as follows: 37 // 38 // 1. On startup the loadBalancerStatusWriter waits to be elected leader. 39 // 2. Once elected leader, the loadBalancerStatusWriter waits to receive a 40 // v1.LoadBalancerStatus value. 41 // 3. Once a v1.LoadBalancerStatus value has been received, the 42 // cached address is updated so that it will be applied to objects 43 // received in any subsequent informer events. 44 // 4. All Ingress, HTTPProxy and Gateway objects are listed from the informer 45 // cache and an attempt is made to update their status with the new 46 // address. This update may end up being a no-op in which case it 47 // doesn't make an API server call. 48 // 5. If the worker is stopped, the informer continues but no further 49 // status updates are made. 50 type loadBalancerStatusWriter struct { 51 log logrus.FieldLogger 52 cache cache.Cache 53 lbStatus chan v1.LoadBalancerStatus 54 statusUpdater k8s.StatusUpdater 55 ingressClassNames []string 56 gatewayControllerName string 57 gatewayRef *types.NamespacedName 58 } 59 60 func (isw *loadBalancerStatusWriter) NeedLeaderElection() bool { 61 return true 62 } 63 64 func (isw *loadBalancerStatusWriter) Start(ctx context.Context) error { 65 u := &k8s.StatusAddressUpdater{ 66 Logger: func() logrus.FieldLogger { 67 // Configure the StatusAddressUpdater logger. 68 log := isw.log.WithField("context", "StatusAddressUpdater") 69 if len(isw.ingressClassNames) > 0 { 70 return log.WithField("target-ingress-classes", isw.ingressClassNames) 71 } 72 73 return log 74 }(), 75 Cache: isw.cache, 76 IngressClassNames: isw.ingressClassNames, 77 GatewayControllerName: isw.gatewayControllerName, 78 GatewayRef: isw.gatewayRef, 79 StatusUpdater: isw.statusUpdater, 80 } 81 82 // Create informers for the types that need load balancer 83 // address status. The cache should have already started 84 // informers, so new informers will auto-start. 85 resources := []client.Object{ 86 &contour_api_v1.HTTPProxy{}, 87 &networking_v1.Ingress{}, 88 } 89 90 // Only create Gateway informer if a controller or specific gateway was provided, 91 // otherwise the API may not exist in the cluster. 92 if len(isw.gatewayControllerName) > 0 || isw.gatewayRef != nil { 93 resources = append(resources, &gatewayapi_v1beta1.Gateway{}) 94 } 95 96 for _, r := range resources { 97 inf, err := isw.cache.GetInformer(context.Background(), r) 98 if err != nil { 99 isw.log.WithError(err).WithField("resource", r).Fatal("failed to create informer") 100 } 101 102 _, err = inf.AddEventHandler(u) 103 if err != nil { 104 isw.log.WithError(err).WithField("resource", r).Fatal("failed to add event handler to informer") 105 } 106 } 107 108 for { 109 select { 110 case <-ctx.Done(): 111 // Once started, there's no way to stop the 112 // informer from here. Clear the load balancer 113 // status so that subsequent informer events 114 // will have no effect. 115 u.Set(v1.LoadBalancerStatus{}) 116 return nil 117 case lbs := <-isw.lbStatus: 118 isw.log.WithField("loadbalancer-address", lbAddress(lbs)). 119 Info("received a new address for status.loadBalancer") 120 121 u.Set(lbs) 122 123 var ingressList networking_v1.IngressList 124 if err := isw.cache.List(context.Background(), &ingressList); err != nil { 125 isw.log.WithError(err).WithField("kind", "Ingress").Error("failed to list objects") 126 } else { 127 for i := range ingressList.Items { 128 u.OnAdd(&ingressList.Items[i], false) 129 } 130 } 131 132 var proxyList contour_api_v1.HTTPProxyList 133 if err := isw.cache.List(context.Background(), &proxyList); err != nil { 134 isw.log.WithError(err).WithField("kind", "HTTPProxy").Error("failed to list objects") 135 } else { 136 for i := range proxyList.Items { 137 u.OnAdd(&proxyList.Items[i], false) 138 } 139 } 140 141 // Only list Gateways if a controller or specific gateway was configured, 142 // otherwise the API may not exist in the cluster. 143 if len(isw.gatewayControllerName) > 0 || isw.gatewayRef != nil { 144 var gatewayList gatewayapi_v1beta1.GatewayList 145 if err := isw.cache.List(context.Background(), &gatewayList); err != nil { 146 isw.log.WithError(err).WithField("kind", "Gateway").Error("failed to list objects") 147 } else { 148 for i := range gatewayList.Items { 149 u.OnAdd(&gatewayList.Items[i], false) 150 } 151 } 152 } 153 } 154 } 155 } 156 157 func parseStatusFlag(status string) v1.LoadBalancerStatus { 158 // Support ','-separated lists. 159 var ingresses []v1.LoadBalancerIngress 160 161 for _, item := range strings.Split(status, ",") { 162 item = strings.TrimSpace(item) 163 if len(item) == 0 { 164 continue 165 } 166 167 // Use the parseability by net.ParseIP as a signal, since we need 168 // to pass a string into the v1.LoadBalancerIngress anyway. 169 if ip := net.ParseIP(item); ip != nil { 170 ingresses = append(ingresses, v1.LoadBalancerIngress{ 171 IP: item, 172 }) 173 } else { 174 ingresses = append(ingresses, v1.LoadBalancerIngress{ 175 Hostname: item, 176 }) 177 } 178 } 179 180 return v1.LoadBalancerStatus{ 181 Ingress: ingresses, 182 } 183 } 184 185 // lbAddress gets the string representation of the first address, for logging. 186 func lbAddress(lb v1.LoadBalancerStatus) string { 187 if len(lb.Ingress) == 0 { 188 return "" 189 } 190 191 if lb.Ingress[0].IP != "" { 192 return lb.Ingress[0].IP 193 } 194 195 return lb.Ingress[0].Hostname 196 }