github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/k8s/status.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"reflect"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/golang/glog"
    12  	conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1"
    13  	v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1"
    14  	conf_v1alpha1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1alpha1"
    15  	k8s_nginx "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned"
    16  	api_v1 "k8s.io/api/core/v1"
    17  	networking "k8s.io/api/networking/v1beta1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	typednetworking "k8s.io/client-go/kubernetes/typed/networking/v1beta1"
    20  
    21  	"k8s.io/apimachinery/pkg/util/intstr"
    22  	"k8s.io/client-go/kubernetes"
    23  	"k8s.io/client-go/tools/cache"
    24  )
    25  
    26  // statusUpdater reports Ingress, VirtualServer and VirtualServerRoute status information via the kubernetes
    27  // API. For external information, it primarily reports the IP or host of the LoadBalancer Service exposing the
    28  // Ingress Controller, or an external IP specified in the ConfigMap.
    29  type statusUpdater struct {
    30  	client                   kubernetes.Interface
    31  	namespace                string
    32  	externalServiceName      string
    33  	externalStatusAddress    string
    34  	externalServiceAddresses []string
    35  	externalServicePorts     string
    36  	bigIPAddress             string
    37  	bigIPPorts               string
    38  	externalEndpoints        []v1.ExternalEndpoint
    39  	status                   []api_v1.LoadBalancerIngress
    40  	keyFunc                  func(obj interface{}) (string, error)
    41  	ingressLister            *storeToIngressLister
    42  	virtualServerLister      cache.Store
    43  	virtualServerRouteLister cache.Store
    44  	transportServerLister    cache.Store
    45  	policyLister             cache.Store
    46  	confClient               k8s_nginx.Interface
    47  }
    48  
    49  func (su *statusUpdater) UpdateExternalEndpointsForResources(resource []Resource) error {
    50  	failed := false
    51  
    52  	for _, r := range resource {
    53  		err := su.UpdateExternalEndpointsForResource(r)
    54  		if err != nil {
    55  			failed = true
    56  		}
    57  	}
    58  
    59  	if failed {
    60  		return fmt.Errorf("not all Resources updated")
    61  	}
    62  
    63  	return nil
    64  }
    65  
    66  func (su *statusUpdater) UpdateExternalEndpointsForResource(r Resource) error {
    67  	switch impl := r.(type) {
    68  	case *IngressConfiguration:
    69  		var ings []networking.Ingress
    70  		ings = append(ings, *impl.Ingress)
    71  
    72  		for _, fm := range impl.Minions {
    73  			ings = append(ings, *fm.Ingress)
    74  		}
    75  
    76  		return su.BulkUpdateIngressStatus(ings)
    77  	case *VirtualServerConfiguration:
    78  		failed := false
    79  
    80  		err := su.updateVirtualServerExternalEndpoints(impl.VirtualServer)
    81  		if err != nil {
    82  			failed = true
    83  		}
    84  
    85  		for _, vsr := range impl.VirtualServerRoutes {
    86  			err := su.updateVirtualServerRouteExternalEndpoints(vsr)
    87  			if err != nil {
    88  				failed = true
    89  			}
    90  		}
    91  
    92  		if failed {
    93  			return fmt.Errorf("not all Resources updated")
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // ClearIngressStatus clears the Ingress status.
   101  func (su *statusUpdater) ClearIngressStatus(ing networking.Ingress) error {
   102  	return su.updateIngressWithStatus(ing, []api_v1.LoadBalancerIngress{})
   103  }
   104  
   105  // UpdateIngressStatus updates the status on the selected Ingress.
   106  func (su *statusUpdater) UpdateIngressStatus(ing networking.Ingress) error {
   107  	return su.updateIngressWithStatus(ing, su.status)
   108  }
   109  
   110  // updateIngressWithStatus sets the provided status on the selected Ingress.
   111  func (su *statusUpdater) updateIngressWithStatus(ing networking.Ingress, status []api_v1.LoadBalancerIngress) error {
   112  	// Get an up-to-date Ingress from the Store
   113  	key, err := su.keyFunc(&ing)
   114  	if err != nil {
   115  		glog.V(3).Infof("error getting key for ing: %v", err)
   116  		return err
   117  	}
   118  	ingCopy, exists, err := su.ingressLister.GetByKeySafe(key)
   119  	if err != nil {
   120  		glog.V(3).Infof("error getting ing from Store by key: %v", err)
   121  		return err
   122  	}
   123  	if !exists {
   124  		glog.V(3).Infof("ing doesn't exist in Store")
   125  		return nil
   126  	}
   127  
   128  	// No need to update status
   129  	if reflect.DeepEqual(ingCopy.Status.LoadBalancer.Ingress, status) {
   130  		return nil
   131  	}
   132  
   133  	ingCopy.Status.LoadBalancer.Ingress = status
   134  	clientIngress := su.client.NetworkingV1beta1().Ingresses(ingCopy.Namespace)
   135  	_, err = clientIngress.UpdateStatus(context.TODO(), ingCopy, metav1.UpdateOptions{})
   136  	if err != nil {
   137  		glog.V(3).Infof("error setting ingress status: %v", err)
   138  		err = su.retryStatusUpdate(clientIngress, ingCopy)
   139  		if err != nil {
   140  			glog.V(3).Infof("error retrying status update: %v", err)
   141  			return err
   142  		}
   143  	}
   144  	glog.V(3).Infof("updated status for ing: %v %v", ing.Namespace, ing.Name)
   145  	return nil
   146  }
   147  
   148  // BulkUpdateIngressStatus sets the status field on the selected Ingresses, specifically
   149  // the External IP field.
   150  func (su *statusUpdater) BulkUpdateIngressStatus(ings []networking.Ingress) error {
   151  	if len(ings) < 1 {
   152  		glog.V(3).Info("no ingresses to update")
   153  		return nil
   154  	}
   155  	failed := false
   156  	for _, ing := range ings {
   157  		err := su.updateIngressWithStatus(ing, su.status)
   158  		if err != nil {
   159  			failed = true
   160  		}
   161  	}
   162  	if failed {
   163  		return fmt.Errorf("not all Ingresses updated")
   164  	}
   165  	return nil
   166  }
   167  
   168  // retryStatusUpdate fetches a fresh copy of the Ingress from the k8s API, checks if it still needs to be
   169  // updated, and then attempts to update. We often need to fetch fresh copies due to the
   170  // k8s API using ResourceVersion to stop updates on stale items.
   171  func (su *statusUpdater) retryStatusUpdate(clientIngress typednetworking.IngressInterface, ingCopy *networking.Ingress) error {
   172  	apiIng, err := clientIngress.Get(context.TODO(), ingCopy.Name, metav1.GetOptions{})
   173  	if err != nil {
   174  		glog.V(3).Infof("error getting ingress resource: %v", err)
   175  		return err
   176  	}
   177  	if !reflect.DeepEqual(ingCopy.Status.LoadBalancer, apiIng.Status.LoadBalancer) {
   178  		glog.V(3).Infof("retrying update status for ingress: %v, %v", ingCopy.Namespace, ingCopy.Name)
   179  		apiIng.Status.LoadBalancer = ingCopy.Status.LoadBalancer
   180  		_, err := clientIngress.UpdateStatus(context.TODO(), apiIng, metav1.UpdateOptions{})
   181  		if err != nil {
   182  			glog.V(3).Infof("update retry failed: %v", err)
   183  		}
   184  		return err
   185  	}
   186  	return nil
   187  }
   188  
   189  // saveStatus saves the string array of IPs or addresses that we will set as status
   190  // on all the Ingresses that we manage.
   191  func (su *statusUpdater) saveStatus(ips []string) {
   192  	statusIngs := []api_v1.LoadBalancerIngress{}
   193  	for _, ip := range ips {
   194  		if net.ParseIP(ip) == nil {
   195  			statusIngs = append(statusIngs, api_v1.LoadBalancerIngress{Hostname: ip})
   196  		} else {
   197  			statusIngs = append(statusIngs, api_v1.LoadBalancerIngress{IP: ip})
   198  		}
   199  	}
   200  	su.status = statusIngs
   201  }
   202  
   203  var intPorts = [2]int32{80, 443}
   204  var stringPorts = [2]string{"http", "https"}
   205  
   206  func isRequiredPort(port intstr.IntOrString) bool {
   207  	if port.Type == intstr.Int {
   208  		for _, p := range intPorts {
   209  			if p == port.IntVal {
   210  				return true
   211  			}
   212  		}
   213  	} else if port.Type == intstr.String {
   214  		for _, p := range stringPorts {
   215  			if p == port.StrVal {
   216  				return true
   217  			}
   218  		}
   219  	}
   220  
   221  	return false
   222  }
   223  
   224  func getExternalServicePorts(svc *api_v1.Service) string {
   225  	var ports []string
   226  	if svc == nil {
   227  		return ""
   228  	}
   229  
   230  	for _, port := range svc.Spec.Ports {
   231  		if isRequiredPort(port.TargetPort) {
   232  			ports = append(ports, strconv.Itoa(int(port.Port)))
   233  		}
   234  	}
   235  
   236  	return fmt.Sprintf("[%v]", strings.Join(ports, ","))
   237  }
   238  
   239  func getExternalServiceAddress(svc *api_v1.Service) []string {
   240  	addresses := []string{}
   241  	if svc == nil {
   242  		return addresses
   243  	}
   244  
   245  	if svc.Spec.Type == api_v1.ServiceTypeExternalName {
   246  		addresses = append(addresses, svc.Spec.ExternalName)
   247  		return addresses
   248  	}
   249  
   250  	for _, ip := range svc.Status.LoadBalancer.Ingress {
   251  		if ip.IP == "" {
   252  			addresses = append(addresses, ip.Hostname)
   253  		} else {
   254  			addresses = append(addresses, ip.IP)
   255  		}
   256  	}
   257  	addresses = append(addresses, svc.Spec.ExternalIPs...)
   258  	return addresses
   259  }
   260  
   261  // SaveStatusFromExternalStatus saves the status from a string.
   262  // For use with the external-status-address ConfigMap setting.
   263  // This method does not update ingress status - statusUpdater.UpdateIngressStatus must be called separately.
   264  func (su *statusUpdater) SaveStatusFromExternalStatus(externalStatusAddress string) {
   265  	su.externalStatusAddress = externalStatusAddress
   266  	if externalStatusAddress == "" {
   267  		// if external-status-address was removed from configMap
   268  
   269  		// fall back on external service if it exists
   270  		if len(su.externalServiceAddresses) > 0 {
   271  			su.saveStatus(su.externalServiceAddresses)
   272  			su.externalEndpoints = su.generateExternalEndpointsFromStatus(su.status)
   273  			return
   274  		}
   275  
   276  		// fall back on IngressLink if it exists
   277  		if su.bigIPAddress != "" {
   278  			su.saveStatus([]string{su.bigIPAddress})
   279  			su.externalEndpoints = su.generateExternalEndpointsFromStatus(su.status)
   280  			return
   281  		}
   282  	}
   283  	ips := []string{}
   284  	ips = append(ips, su.externalStatusAddress)
   285  	su.saveStatus(ips)
   286  	su.externalEndpoints = su.generateExternalEndpointsFromStatus(su.status)
   287  }
   288  
   289  // ClearStatusFromExternalService clears the saved status from the External Service
   290  func (su *statusUpdater) ClearStatusFromExternalService() {
   291  	su.SaveStatusFromExternalService(nil)
   292  }
   293  
   294  // SaveStatusFromExternalService saves the external IP or address from the service.
   295  // This method does not update ingress status - UpdateIngressStatus must be called separately.
   296  func (su *statusUpdater) SaveStatusFromExternalService(svc *api_v1.Service) {
   297  	ips := getExternalServiceAddress(svc)
   298  	su.externalServiceAddresses = ips
   299  	ports := getExternalServicePorts(svc)
   300  	su.externalServicePorts = ports
   301  	if su.externalStatusAddress != "" {
   302  		glog.V(3).Info("skipping external service address/ports - external-status-address is set and takes precedence")
   303  		return
   304  	}
   305  	su.saveStatus(ips)
   306  	su.externalEndpoints = su.generateExternalEndpointsFromStatus(su.status)
   307  }
   308  
   309  func (su *statusUpdater) SaveStatusFromIngressLink(ip string) {
   310  	su.bigIPAddress = ip
   311  	su.bigIPPorts = "[80,443]"
   312  
   313  	if su.externalStatusAddress != "" {
   314  		glog.V(3).Info("skipping IngressLink address - external-status-address is set and takes precedence")
   315  		return
   316  	}
   317  
   318  	ips := []string{su.bigIPAddress}
   319  	su.saveStatus(ips)
   320  	su.externalEndpoints = su.generateExternalEndpointsFromStatus(su.status)
   321  }
   322  
   323  func (su *statusUpdater) ClearStatusFromIngressLink() {
   324  	su.bigIPAddress = ""
   325  	su.bigIPPorts = ""
   326  
   327  	if su.externalStatusAddress != "" {
   328  		glog.V(3).Info("skipping IngressLink address - external-status-address is set and takes precedence")
   329  		return
   330  	}
   331  
   332  	ips := []string{}
   333  	su.saveStatus(ips)
   334  	su.externalEndpoints = su.generateExternalEndpointsFromStatus(su.status)
   335  }
   336  
   337  func (su *statusUpdater) retryUpdateTransportServerStatus(tsCopy *conf_v1alpha1.TransportServer) error {
   338  	ts, err := su.confClient.K8sV1alpha1().TransportServers(tsCopy.Namespace).Get(context.TODO(), tsCopy.Name, metav1.GetOptions{})
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	ts.Status = tsCopy.Status
   344  	_, err = su.confClient.K8sV1alpha1().TransportServers(ts.Namespace).UpdateStatus(context.TODO(), ts, metav1.UpdateOptions{})
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	return nil
   350  }
   351  
   352  func (su *statusUpdater) retryUpdateVirtualServerStatus(vsCopy *conf_v1.VirtualServer) error {
   353  	vs, err := su.confClient.K8sV1().VirtualServers(vsCopy.Namespace).Get(context.TODO(), vsCopy.Name, metav1.GetOptions{})
   354  	if err != nil {
   355  		return err
   356  	}
   357  
   358  	vs.Status = vsCopy.Status
   359  	_, err = su.confClient.K8sV1().VirtualServers(vs.Namespace).UpdateStatus(context.TODO(), vs, metav1.UpdateOptions{})
   360  	if err != nil {
   361  		return err
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  func (su *statusUpdater) retryUpdateVirtualServerRouteStatus(vsrCopy *conf_v1.VirtualServerRoute) error {
   368  	vsr, err := su.confClient.K8sV1().VirtualServerRoutes(vsrCopy.Namespace).Get(context.TODO(), vsrCopy.Name, metav1.GetOptions{})
   369  	if err != nil {
   370  		return err
   371  	}
   372  
   373  	vsr.Status = vsrCopy.Status
   374  	_, err = su.confClient.K8sV1().VirtualServerRoutes(vsr.Namespace).UpdateStatus(context.TODO(), vsr, metav1.UpdateOptions{})
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	return nil
   380  }
   381  
   382  func hasVsStatusChanged(vs *conf_v1.VirtualServer, state string, reason string, message string) bool {
   383  	if vs.Status.State != state {
   384  		return true
   385  	}
   386  
   387  	if vs.Status.Reason != reason {
   388  		return true
   389  	}
   390  
   391  	if vs.Status.Message != message {
   392  		return true
   393  	}
   394  
   395  	return false
   396  }
   397  
   398  // UpdateTransportServerStatus updates the status of a TransportServer.
   399  func (su *statusUpdater) UpdateTransportServerStatus(ts *conf_v1alpha1.TransportServer, state string, reason string, message string) error {
   400  	tsLatest, exists, err := su.transportServerLister.Get(ts)
   401  	if err != nil {
   402  		glog.V(3).Infof("error getting TransportServer from Store: %v", err)
   403  		return err
   404  	}
   405  	if !exists {
   406  		glog.V(3).Infof("TransportServer doesn't exist in Store")
   407  		return nil
   408  	}
   409  
   410  	if !hasTsStatusChanged(tsLatest.(*conf_v1alpha1.TransportServer), state, reason, message) {
   411  		return nil
   412  	}
   413  
   414  	tsCopy := tsLatest.(*conf_v1alpha1.TransportServer).DeepCopy()
   415  	tsCopy.Status.State = state
   416  	tsCopy.Status.Reason = reason
   417  	tsCopy.Status.Message = message
   418  
   419  	_, err = su.confClient.K8sV1alpha1().TransportServers(tsCopy.Namespace).UpdateStatus(context.TODO(), tsCopy, metav1.UpdateOptions{})
   420  	if err != nil {
   421  		glog.V(3).Infof("error setting TransportServer %v/%v status, retrying: %v", tsCopy.Namespace, tsCopy.Name, err)
   422  		return su.retryUpdateTransportServerStatus(tsCopy)
   423  	}
   424  	return err
   425  }
   426  
   427  func hasTsStatusChanged(ts *conf_v1alpha1.TransportServer, state string, reason string, message string) bool {
   428  	if ts.Status.State != state {
   429  		return true
   430  	}
   431  	if ts.Status.Reason != reason {
   432  		return true
   433  	}
   434  	if ts.Status.Message != message {
   435  		return true
   436  	}
   437  	return false
   438  }
   439  
   440  // UpdateVirtualServerStatus updates the status of a VirtualServer.
   441  func (su *statusUpdater) UpdateVirtualServerStatus(vs *conf_v1.VirtualServer, state string, reason string, message string) error {
   442  	// Get an up-to-date VirtualServer from the Store
   443  	vsLatest, exists, err := su.virtualServerLister.Get(vs)
   444  	if err != nil {
   445  		glog.V(3).Infof("error getting VirtualServer from Store: %v", err)
   446  		return err
   447  	}
   448  	if !exists {
   449  		glog.V(3).Infof("VirtualServer doesn't exist in Store")
   450  		return nil
   451  	}
   452  
   453  	vsCopy := vsLatest.(*conf_v1.VirtualServer).DeepCopy()
   454  
   455  	if !hasVsStatusChanged(vsCopy, state, reason, message) {
   456  		return nil
   457  	}
   458  
   459  	vsCopy.Status.State = state
   460  	vsCopy.Status.Reason = reason
   461  	vsCopy.Status.Message = message
   462  	vsCopy.Status.ExternalEndpoints = su.externalEndpoints
   463  
   464  	_, err = su.confClient.K8sV1().VirtualServers(vsCopy.Namespace).UpdateStatus(context.TODO(), vsCopy, metav1.UpdateOptions{})
   465  	if err != nil {
   466  		glog.V(3).Infof("error setting VirtualServer %v/%v status, retrying: %v", vsCopy.Namespace, vsCopy.Name, err)
   467  		return su.retryUpdateVirtualServerStatus(vsCopy)
   468  	}
   469  	return err
   470  }
   471  
   472  func hasVsrStatusChanged(vsr *conf_v1.VirtualServerRoute, state string, reason string, message string, referencedByString string) bool {
   473  	if vsr.Status.State != state {
   474  		return true
   475  	}
   476  
   477  	if vsr.Status.Reason != reason {
   478  		return true
   479  	}
   480  
   481  	if vsr.Status.Message != message {
   482  		return true
   483  	}
   484  
   485  	if referencedByString != "" && vsr.Status.ReferencedBy != referencedByString {
   486  		return true
   487  	}
   488  
   489  	return false
   490  }
   491  
   492  // UpdateVirtualServerRouteStatusWithReferencedBy updates the status of a VirtualServerRoute, including the referencedBy field.
   493  func (su *statusUpdater) UpdateVirtualServerRouteStatusWithReferencedBy(vsr *conf_v1.VirtualServerRoute, state string, reason string, message string, referencedBy []*v1.VirtualServer) error {
   494  	var referencedByString string
   495  	if len(referencedBy) != 0 {
   496  		vs := referencedBy[0]
   497  		referencedByString = fmt.Sprintf("%v/%v", vs.Namespace, vs.Name)
   498  	}
   499  
   500  	// Get an up-to-date VirtualServerRoute from the Store
   501  	vsrLatest, exists, err := su.virtualServerRouteLister.Get(vsr)
   502  	if err != nil {
   503  		glog.V(3).Infof("error getting VirtualServerRoute from Store: %v", err)
   504  		return err
   505  	}
   506  	if !exists {
   507  		glog.V(3).Infof("VirtualServerRoute doesn't exist in Store")
   508  		return nil
   509  	}
   510  
   511  	vsrCopy := vsrLatest.(*conf_v1.VirtualServerRoute).DeepCopy()
   512  
   513  	vsrCopy.Status.State = state
   514  	vsrCopy.Status.Reason = reason
   515  	vsrCopy.Status.Message = message
   516  	vsrCopy.Status.ReferencedBy = referencedByString
   517  	vsrCopy.Status.ExternalEndpoints = su.externalEndpoints
   518  
   519  	_, err = su.confClient.K8sV1().VirtualServerRoutes(vsrCopy.Namespace).UpdateStatus(context.TODO(), vsrCopy, metav1.UpdateOptions{})
   520  	if err != nil {
   521  		glog.V(3).Infof("error setting VirtualServerRoute %v/%v status, retrying: %v", vsrCopy.Namespace, vsrCopy.Name, err)
   522  		return su.retryUpdateVirtualServerRouteStatus(vsrCopy)
   523  	}
   524  	return err
   525  }
   526  
   527  // UpdateVirtualServerRouteStatus updates the status of a VirtualServerRoute.
   528  // This method does not clear or update the referencedBy field of the status.
   529  // If you need to update the referencedBy field, use UpdateVirtualServerRouteStatusWithReferencedBy instead.
   530  func (su *statusUpdater) UpdateVirtualServerRouteStatus(vsr *conf_v1.VirtualServerRoute, state string, reason string, message string) error {
   531  	// Get an up-to-date VirtualServerRoute from the Store
   532  	vsrLatest, exists, err := su.virtualServerRouteLister.Get(vsr)
   533  	if err != nil {
   534  		glog.V(3).Infof("error getting VirtualServerRoute from Store: %v", err)
   535  		return err
   536  	}
   537  	if !exists {
   538  		glog.V(3).Infof("VirtualServerRoute doesn't exist in Store")
   539  		return nil
   540  	}
   541  
   542  	vsrCopy := vsrLatest.(*conf_v1.VirtualServerRoute).DeepCopy()
   543  
   544  	if !hasVsrStatusChanged(vsrCopy, state, reason, message, "") {
   545  		return nil
   546  	}
   547  
   548  	vsrCopy.Status.State = state
   549  	vsrCopy.Status.Reason = reason
   550  	vsrCopy.Status.Message = message
   551  	vsrCopy.Status.ExternalEndpoints = su.externalEndpoints
   552  
   553  	_, err = su.confClient.K8sV1().VirtualServerRoutes(vsrCopy.Namespace).UpdateStatus(context.TODO(), vsrCopy, metav1.UpdateOptions{})
   554  	if err != nil {
   555  		glog.V(3).Infof("error setting VirtualServerRoute %v/%v status, retrying: %v", vsrCopy.Namespace, vsrCopy.Name, err)
   556  		return su.retryUpdateVirtualServerRouteStatus(vsrCopy)
   557  	}
   558  	return err
   559  }
   560  
   561  func (su *statusUpdater) updateVirtualServerExternalEndpoints(vs *conf_v1.VirtualServer) error {
   562  	// Get a pristine VirtualServer from the Store
   563  	vsLatest, exists, err := su.virtualServerLister.Get(vs)
   564  	if err != nil {
   565  		glog.V(3).Infof("error getting VirtualServer from Store: %v", err)
   566  		return err
   567  	}
   568  	if !exists {
   569  		glog.V(3).Infof("VirtualServer doesn't exist in Store")
   570  		return nil
   571  	}
   572  
   573  	vsCopy := vsLatest.(*conf_v1.VirtualServer).DeepCopy()
   574  	vsCopy.Status.ExternalEndpoints = su.externalEndpoints
   575  
   576  	_, err = su.confClient.K8sV1().VirtualServers(vsCopy.Namespace).UpdateStatus(context.TODO(), vsCopy, metav1.UpdateOptions{})
   577  	if err != nil {
   578  		glog.V(3).Infof("error setting VirtualServer %v/%v status, retrying: %v", vsCopy.Namespace, vsCopy.Name, err)
   579  		return su.retryUpdateVirtualServerStatus(vsCopy)
   580  	}
   581  	return err
   582  }
   583  
   584  func (su *statusUpdater) updateVirtualServerRouteExternalEndpoints(vsr *conf_v1.VirtualServerRoute) error {
   585  	// Get an up-to-date VirtualServerRoute from the Store
   586  	vsrLatest, exists, err := su.virtualServerRouteLister.Get(vsr)
   587  	if err != nil {
   588  		glog.V(3).Infof("error getting VirtualServerRoute from Store: %v", err)
   589  		return err
   590  	}
   591  	if !exists {
   592  		glog.V(3).Infof("VirtualServerRoute doesn't exist in Store")
   593  		return nil
   594  	}
   595  
   596  	vsrCopy := vsrLatest.(*conf_v1.VirtualServerRoute).DeepCopy()
   597  	vsrCopy.Status.ExternalEndpoints = su.externalEndpoints
   598  
   599  	_, err = su.confClient.K8sV1().VirtualServerRoutes(vsrCopy.Namespace).UpdateStatus(context.TODO(), vsrCopy, metav1.UpdateOptions{})
   600  	if err != nil {
   601  		glog.V(3).Infof("error setting VirtualServerRoute %v/%v status, retrying: %v", vsrCopy.Namespace, vsrCopy.Name, err)
   602  		return su.retryUpdateVirtualServerRouteStatus(vsrCopy)
   603  	}
   604  	return err
   605  }
   606  
   607  func (su *statusUpdater) generateExternalEndpointsFromStatus(status []api_v1.LoadBalancerIngress) []conf_v1.ExternalEndpoint {
   608  	var externalEndpoints []conf_v1.ExternalEndpoint
   609  	for _, lb := range status {
   610  		ports := su.externalServicePorts
   611  		if su.bigIPPorts != "" {
   612  			ports = su.bigIPPorts
   613  		}
   614  
   615  		endpoint := conf_v1.ExternalEndpoint{IP: lb.IP, Ports: ports}
   616  		externalEndpoints = append(externalEndpoints, endpoint)
   617  	}
   618  
   619  	return externalEndpoints
   620  }
   621  
   622  func hasPolicyStatusChanged(pol *v1.Policy, state string, reason string, message string) bool {
   623  	return pol.Status.State != state || pol.Status.Reason != reason || pol.Status.Message != message
   624  }
   625  
   626  // UpdatePolicyStatus updates the status of a Policy.
   627  func (su *statusUpdater) UpdatePolicyStatus(pol *v1.Policy, state string, reason string, message string) error {
   628  	// Get an up-to-date Policy from the Store
   629  	polLatest, exists, err := su.policyLister.Get(pol)
   630  	if err != nil {
   631  		glog.V(3).Infof("error getting policy from Store: %v", err)
   632  		return err
   633  	}
   634  	if !exists {
   635  		glog.V(3).Infof("Policy doesn't exist in Store")
   636  		return nil
   637  	}
   638  
   639  	polCopy := polLatest.(*v1.Policy)
   640  
   641  	if !hasPolicyStatusChanged(polCopy, state, reason, message) {
   642  		return nil
   643  	}
   644  
   645  	polCopy.Status.State = state
   646  	polCopy.Status.Reason = reason
   647  	polCopy.Status.Message = message
   648  
   649  	_, err = su.confClient.K8sV1().Policies(polCopy.Namespace).UpdateStatus(context.TODO(), polCopy, metav1.UpdateOptions{})
   650  	if err != nil {
   651  		glog.V(3).Infof("error setting Policy %v/%v status, retrying: %v", polCopy.Namespace, polCopy.Name, err)
   652  		return su.retryUpdatePolicyStatus(polCopy)
   653  	}
   654  
   655  	return nil
   656  }
   657  
   658  func (su *statusUpdater) retryUpdatePolicyStatus(polCopy *v1.Policy) error {
   659  	pol, err := su.confClient.K8sV1().Policies(polCopy.Namespace).Get(context.TODO(), polCopy.Name, metav1.GetOptions{})
   660  	if err != nil {
   661  		return err
   662  	}
   663  
   664  	pol.Status = polCopy.Status
   665  	_, err = su.confClient.K8sV1().Policies(pol.Namespace).UpdateStatus(context.TODO(), pol, metav1.UpdateOptions{})
   666  	if err != nil {
   667  		return err
   668  	}
   669  
   670  	return nil
   671  }