2015-03-24 17:32:43 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors.
|
2015-03-24 17:32:43 +00:00
|
|
|
|
|
|
|
Licensed 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.
|
|
|
|
*/
|
|
|
|
|
2015-10-10 03:58:57 +00:00
|
|
|
package service
|
2015-03-24 17:32:43 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-04-09 20:48:27 +00:00
|
|
|
"sort"
|
2015-03-24 17:32:43 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2016-02-26 19:35:33 +00:00
|
|
|
"reflect"
|
|
|
|
|
2015-08-05 22:05:17 +00:00
|
|
|
"github.com/golang/glog"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/errors"
|
2015-09-03 21:40:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/cache"
|
2016-02-05 21:58:03 +00:00
|
|
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
2016-10-21 22:24:05 +00:00
|
|
|
unversioned_core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
2015-09-03 21:40:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/record"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
2016-07-26 05:35:40 +00:00
|
|
|
"k8s.io/kubernetes/pkg/controller"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/fields"
|
2016-07-26 05:35:40 +00:00
|
|
|
pkg_runtime "k8s.io/kubernetes/pkg/runtime"
|
2016-04-13 18:38:32 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/metrics"
|
2016-01-15 07:32:10 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/runtime"
|
2016-07-26 05:35:40 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/wait"
|
|
|
|
"k8s.io/kubernetes/pkg/util/workqueue"
|
|
|
|
"k8s.io/kubernetes/pkg/watch"
|
2015-03-24 17:32:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2016-07-26 05:35:40 +00:00
|
|
|
// Interval of synchoronizing service status from apiserver
|
|
|
|
serviceSyncPeriod = 30 * time.Second
|
|
|
|
// Interval of synchoronizing node status from apiserver
|
|
|
|
nodeSyncPeriod = 100 * time.Second
|
2015-04-09 20:48:27 +00:00
|
|
|
|
2015-05-26 21:09:05 +00:00
|
|
|
// How long to wait before retrying the processing of a service change.
|
|
|
|
// If this changes, the sleep in hack/jenkins/e2e.sh before downing a cluster
|
|
|
|
// should be changed appropriately.
|
2016-02-25 14:51:11 +00:00
|
|
|
minRetryDelay = 5 * time.Second
|
|
|
|
maxRetryDelay = 300 * time.Second
|
2015-05-26 21:09:05 +00:00
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
clientRetryCount = 5
|
|
|
|
clientRetryInterval = 5 * time.Second
|
|
|
|
|
|
|
|
retryable = true
|
|
|
|
notRetryable = false
|
2016-02-25 14:51:11 +00:00
|
|
|
|
|
|
|
doNotRetry = time.Duration(0)
|
2015-03-24 17:32:43 +00:00
|
|
|
)
|
|
|
|
|
2015-04-09 20:48:27 +00:00
|
|
|
type cachedService struct {
|
2016-07-26 05:35:40 +00:00
|
|
|
// The cached state of the service
|
|
|
|
state *api.Service
|
2016-02-25 14:51:11 +00:00
|
|
|
// Controls error back-off
|
|
|
|
lastRetryDelay time.Duration
|
2015-04-09 20:48:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type serviceCache struct {
|
|
|
|
mu sync.Mutex // protects serviceMap
|
|
|
|
serviceMap map[string]*cachedService
|
|
|
|
}
|
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
type ServiceController struct {
|
2016-08-23 06:47:50 +00:00
|
|
|
cloud cloudprovider.Interface
|
|
|
|
knownHosts []string
|
|
|
|
servicesToUpdate []*api.Service
|
|
|
|
kubeClient clientset.Interface
|
|
|
|
clusterName string
|
|
|
|
balancer cloudprovider.LoadBalancer
|
|
|
|
zone cloudprovider.Zone
|
|
|
|
cache *serviceCache
|
2016-07-26 05:35:40 +00:00
|
|
|
// A store of services, populated by the serviceController
|
|
|
|
serviceStore cache.StoreToServiceLister
|
|
|
|
// Watches changes to all services
|
2016-09-14 18:35:38 +00:00
|
|
|
serviceController *cache.Controller
|
2016-07-26 05:35:40 +00:00
|
|
|
eventBroadcaster record.EventBroadcaster
|
|
|
|
eventRecorder record.EventRecorder
|
|
|
|
nodeLister cache.StoreToNodeLister
|
|
|
|
// services that need to be synced
|
|
|
|
workingQueue workqueue.DelayingInterface
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new service controller to keep cloud provider service resources
|
2015-09-30 01:42:37 +00:00
|
|
|
// (like load balancers) in sync with the registry.
|
2016-07-26 05:35:40 +00:00
|
|
|
func New(cloud cloudprovider.Interface, kubeClient clientset.Interface, clusterName string) (*ServiceController, error) {
|
2015-05-27 21:19:46 +00:00
|
|
|
broadcaster := record.NewBroadcaster()
|
2016-03-23 23:45:24 +00:00
|
|
|
broadcaster.StartRecordingToSink(&unversioned_core.EventSinkImpl{Interface: kubeClient.Core().Events("")})
|
2015-05-27 21:19:46 +00:00
|
|
|
recorder := broadcaster.NewRecorder(api.EventSource{Component: "service-controller"})
|
|
|
|
|
2016-10-13 12:56:07 +00:00
|
|
|
if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil {
|
|
|
|
metrics.RegisterMetricAndTrackRateLimiterUsage("service_controller", kubeClient.Core().RESTClient().GetRateLimiter())
|
2016-04-13 18:38:32 +00:00
|
|
|
}
|
|
|
|
|
2016-07-26 05:35:40 +00:00
|
|
|
s := &ServiceController{
|
2015-05-27 21:19:46 +00:00
|
|
|
cloud: cloud,
|
2016-07-26 05:35:40 +00:00
|
|
|
knownHosts: []string{},
|
2015-05-27 21:19:46 +00:00
|
|
|
kubeClient: kubeClient,
|
|
|
|
clusterName: clusterName,
|
|
|
|
cache: &serviceCache{serviceMap: make(map[string]*cachedService)},
|
|
|
|
eventBroadcaster: broadcaster,
|
|
|
|
eventRecorder: recorder,
|
2015-06-08 18:30:34 +00:00
|
|
|
nodeLister: cache.StoreToNodeLister{
|
|
|
|
Store: cache.NewStore(cache.MetaNamespaceKeyFunc),
|
|
|
|
},
|
2016-07-26 05:35:40 +00:00
|
|
|
workingQueue: workqueue.NewDelayingQueue(),
|
|
|
|
}
|
2016-09-16 17:38:50 +00:00
|
|
|
s.serviceStore.Indexer, s.serviceController = cache.NewIndexerInformer(
|
2016-07-26 05:35:40 +00:00
|
|
|
&cache.ListWatch{
|
|
|
|
ListFunc: func(options api.ListOptions) (pkg_runtime.Object, error) {
|
|
|
|
return s.kubeClient.Core().Services(api.NamespaceAll).List(options)
|
|
|
|
},
|
|
|
|
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
|
|
|
|
return s.kubeClient.Core().Services(api.NamespaceAll).Watch(options)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&api.Service{},
|
|
|
|
serviceSyncPeriod,
|
2016-09-14 18:35:38 +00:00
|
|
|
cache.ResourceEventHandlerFuncs{
|
2016-07-26 05:35:40 +00:00
|
|
|
AddFunc: s.enqueueService,
|
|
|
|
UpdateFunc: func(old, cur interface{}) {
|
|
|
|
oldSvc, ok1 := old.(*api.Service)
|
|
|
|
curSvc, ok2 := cur.(*api.Service)
|
|
|
|
if ok1 && ok2 && s.needsUpdate(oldSvc, curSvc) {
|
|
|
|
s.enqueueService(cur)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
DeleteFunc: s.enqueueService,
|
|
|
|
},
|
2016-09-16 17:38:50 +00:00
|
|
|
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
2016-07-26 05:35:40 +00:00
|
|
|
)
|
|
|
|
if err := s.init(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// obj could be an *api.Service, or a DeletionFinalStateUnknown marker item.
|
|
|
|
func (s *ServiceController) enqueueService(obj interface{}) {
|
|
|
|
key, err := controller.KeyFunc(obj)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Couldn't get key for object %#v: %v", obj, err)
|
|
|
|
return
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
s.workingQueue.Add(key)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts a background goroutine that watches for changes to services that
|
2015-09-30 01:42:37 +00:00
|
|
|
// have (or had) LoadBalancers=true and ensures that they have
|
2015-03-24 17:32:43 +00:00
|
|
|
// load balancers created and deleted appropriately.
|
2015-07-27 18:03:13 +00:00
|
|
|
// serviceSyncPeriod controls how often we check the cluster's services to
|
2015-09-30 01:42:37 +00:00
|
|
|
// ensure that the correct load balancers exist.
|
2015-04-22 20:54:44 +00:00
|
|
|
// nodeSyncPeriod controls how often we check the cluster's nodes to determine
|
2015-09-30 01:42:37 +00:00
|
|
|
// if load balancers need to be updated to point to a new set.
|
2015-06-08 18:30:34 +00:00
|
|
|
//
|
|
|
|
// It's an error to call Run() more than once for a given ServiceController
|
|
|
|
// object.
|
2016-07-26 05:35:40 +00:00
|
|
|
func (s *ServiceController) Run(workers int) {
|
|
|
|
defer runtime.HandleCrash()
|
|
|
|
go s.serviceController.Run(wait.NeverStop)
|
|
|
|
for i := 0; i < workers; i++ {
|
|
|
|
go wait.Until(s.worker, time.Second, wait.NeverStop)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
2016-10-13 12:56:07 +00:00
|
|
|
nodeLW := cache.NewListWatchFromClient(s.kubeClient.Core().RESTClient(), "nodes", api.NamespaceAll, fields.Everything())
|
2016-07-26 05:35:40 +00:00
|
|
|
cache.NewReflector(nodeLW, &api.Node{}, s.nodeLister.Store, 0).Run()
|
|
|
|
go wait.Until(s.nodeSyncLoop, nodeSyncPeriod, wait.NeverStop)
|
|
|
|
}
|
2015-03-24 17:32:43 +00:00
|
|
|
|
2016-07-26 05:35:40 +00:00
|
|
|
// worker runs a worker thread that just dequeues items, processes them, and marks them done.
|
|
|
|
// It enforces that the syncHandler is never invoked concurrently with the same key.
|
|
|
|
func (s *ServiceController) worker() {
|
|
|
|
for {
|
|
|
|
func() {
|
|
|
|
key, quit := s.workingQueue.Get()
|
|
|
|
if quit {
|
|
|
|
return
|
2015-06-08 18:30:34 +00:00
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
defer s.workingQueue.Done(key)
|
|
|
|
err := s.syncService(key.(string))
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Error syncing service: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
2015-04-09 20:48:27 +00:00
|
|
|
}
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ServiceController) init() error {
|
|
|
|
if s.cloud == nil {
|
2016-07-14 15:48:45 +00:00
|
|
|
return fmt.Errorf("WARNING: no cloud provider provided, services of type LoadBalancer will fail.")
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
2015-09-28 20:57:58 +00:00
|
|
|
balancer, ok := s.cloud.LoadBalancer()
|
2015-03-24 17:32:43 +00:00
|
|
|
if !ok {
|
2015-09-28 20:57:58 +00:00
|
|
|
return fmt.Errorf("the cloud provider does not support external load balancers.")
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
s.balancer = balancer
|
|
|
|
|
|
|
|
zones, ok := s.cloud.Zones()
|
|
|
|
if !ok {
|
2015-09-30 01:42:37 +00:00
|
|
|
return fmt.Errorf("the cloud provider does not support zone enumeration, which is required for creating load balancers.")
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
zone, err := zones.GetZone()
|
|
|
|
if err != nil {
|
2015-09-30 01:42:37 +00:00
|
|
|
return fmt.Errorf("failed to get zone from cloud provider, will not be able to create load balancers: %v", err)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
s.zone = zone
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-26 05:35:40 +00:00
|
|
|
// Returns an error if processing the service update failed, along with a time.Duration
|
2016-02-25 14:51:11 +00:00
|
|
|
// indicating whether processing should be retried; zero means no-retry; otherwise
|
|
|
|
// we should retry in that Duration.
|
2016-07-26 05:35:40 +00:00
|
|
|
func (s *ServiceController) processServiceUpdate(cachedService *cachedService, service *api.Service, key string) (error, time.Duration) {
|
2016-02-25 14:51:11 +00:00
|
|
|
|
2016-07-26 05:35:40 +00:00
|
|
|
// cache the service, we need the info for service deletion
|
|
|
|
cachedService.state = service
|
|
|
|
err, retry := s.createLoadBalancerIfNeeded(key, service)
|
2016-02-26 19:35:33 +00:00
|
|
|
if err != nil {
|
|
|
|
message := "Error creating load balancer"
|
|
|
|
if retry {
|
|
|
|
message += " (will retry): "
|
|
|
|
} else {
|
|
|
|
message += " (will not retry): "
|
|
|
|
}
|
|
|
|
message += err.Error()
|
|
|
|
s.eventRecorder.Event(service, api.EventTypeWarning, "CreatingLoadBalancerFailed", message)
|
|
|
|
|
2016-02-25 14:51:11 +00:00
|
|
|
return err, cachedService.nextRetryDelay()
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
2016-02-26 19:35:33 +00:00
|
|
|
// Always update the cache upon success.
|
|
|
|
// NOTE: Since we update the cached service if and only if we successfully
|
|
|
|
// processed it, a cached service being nil implies that it hasn't yet
|
|
|
|
// been successfully processed.
|
2016-07-26 05:35:40 +00:00
|
|
|
s.cache.set(key, cachedService)
|
2016-02-26 19:35:33 +00:00
|
|
|
|
2016-02-25 14:51:11 +00:00
|
|
|
cachedService.resetRetryDelay()
|
|
|
|
return nil, doNotRetry
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns whatever error occurred along with a boolean indicator of whether it
|
|
|
|
// should be retried.
|
2016-07-26 05:35:40 +00:00
|
|
|
func (s *ServiceController) createLoadBalancerIfNeeded(key string, service *api.Service) (error, bool) {
|
2015-06-13 16:24:26 +00:00
|
|
|
|
2015-09-28 20:57:58 +00:00
|
|
|
// Note: It is safe to just call EnsureLoadBalancer. But, on some clouds that requires a delete & create,
|
2015-06-13 16:24:26 +00:00
|
|
|
// which may involve service interruption. Also, we would like user-friendly events.
|
|
|
|
|
|
|
|
// Save the state so we can avoid a write if it doesn't change
|
|
|
|
previousState := api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer)
|
|
|
|
|
2016-07-26 05:35:40 +00:00
|
|
|
if !wantsLoadBalancer(service) {
|
|
|
|
needDelete := true
|
|
|
|
_, exists, err := s.balancer.GetLoadBalancer(s.clusterName, service)
|
2016-07-11 06:13:25 +00:00
|
|
|
if err != nil {
|
2016-07-26 05:35:40 +00:00
|
|
|
return fmt.Errorf("Error getting LB for service %s: %v", key, err), retryable
|
2016-07-11 06:13:25 +00:00
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
if !exists {
|
|
|
|
needDelete = false
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
2015-06-13 16:24:26 +00:00
|
|
|
|
|
|
|
if needDelete {
|
2016-07-26 05:35:40 +00:00
|
|
|
glog.Infof("Deleting existing load balancer for service %s that no longer needs a load balancer.", key)
|
2015-11-13 22:30:01 +00:00
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "DeletingLoadBalancer", "Deleting load balancer")
|
2016-05-29 03:54:07 +00:00
|
|
|
if err := s.balancer.EnsureLoadBalancerDeleted(s.clusterName, service); err != nil {
|
2015-03-24 17:32:43 +00:00
|
|
|
return err, retryable
|
|
|
|
}
|
2015-11-13 22:30:01 +00:00
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "DeletedLoadBalancer", "Deleted load balancer")
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 22:59:45 +00:00
|
|
|
service.Status.LoadBalancer = api.LoadBalancerStatus{}
|
2016-07-26 05:35:40 +00:00
|
|
|
} else {
|
|
|
|
glog.V(2).Infof("Ensuring LB for service %s", key)
|
|
|
|
|
|
|
|
// TODO: We could do a dry-run here if wanted to avoid the spurious cloud-calls & events when we restart
|
|
|
|
|
|
|
|
// The load balancer doesn't exist yet, so create it.
|
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "CreatingLoadBalancer", "Creating load balancer")
|
|
|
|
err := s.createLoadBalancer(service)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to create load balancer for service %s: %v", key, err), retryable
|
|
|
|
}
|
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "CreatedLoadBalancer", "Created load balancer")
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 21:33:29 +00:00
|
|
|
// Write the state if changed
|
2015-05-22 22:59:45 +00:00
|
|
|
// TODO: Be careful here ... what if there were other changes to the service?
|
2016-07-26 05:35:40 +00:00
|
|
|
if !api.LoadBalancerStatusEqual(previousState, &service.Status.LoadBalancer) {
|
2015-05-22 22:59:45 +00:00
|
|
|
if err := s.persistUpdate(service); err != nil {
|
2016-07-26 05:35:40 +00:00
|
|
|
return fmt.Errorf("Failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable
|
2015-05-22 22:59:45 +00:00
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
} else {
|
|
|
|
glog.V(2).Infof("Not persisting unchanged LoadBalancerStatus to registry.")
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, notRetryable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ServiceController) persistUpdate(service *api.Service) error {
|
|
|
|
var err error
|
|
|
|
for i := 0; i < clientRetryCount; i++ {
|
2016-02-03 21:21:05 +00:00
|
|
|
_, err = s.kubeClient.Core().Services(service.Namespace).UpdateStatus(service)
|
2016-07-26 05:35:40 +00:00
|
|
|
if err == nil {
|
2015-03-24 17:32:43 +00:00
|
|
|
return nil
|
2016-07-26 05:35:40 +00:00
|
|
|
}
|
|
|
|
// If the object no longer exists, we don't want to recreate it. Just bail
|
|
|
|
// out so that we can process the delete, which we should soon be receiving
|
|
|
|
// if we haven't already.
|
|
|
|
if errors.IsNotFound(err) {
|
2016-02-05 21:16:32 +00:00
|
|
|
glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v",
|
|
|
|
service.Namespace, service.Name, err)
|
2015-04-09 20:48:27 +00:00
|
|
|
return nil
|
2016-07-26 05:35:40 +00:00
|
|
|
}
|
|
|
|
// TODO: Try to resolve the conflict if the change was unrelated to load
|
|
|
|
// balancer status. For now, just rely on the fact that we'll
|
|
|
|
// also process the update that caused the resource version to change.
|
|
|
|
if errors.IsConflict(err) {
|
2016-02-05 21:16:32 +00:00
|
|
|
glog.V(4).Infof("Not persisting update to service '%s/%s' that has been changed since we received it: %v",
|
|
|
|
service.Namespace, service.Name, err)
|
2015-04-09 20:48:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
2016-02-05 21:16:32 +00:00
|
|
|
glog.Warningf("Failed to persist updated LoadBalancerStatus to service '%s/%s' after creating its load balancer: %v",
|
|
|
|
service.Namespace, service.Name, err)
|
2015-03-24 17:32:43 +00:00
|
|
|
time.Sleep(clientRetryInterval)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
func (s *ServiceController) createLoadBalancer(service *api.Service) error {
|
2015-06-08 18:30:34 +00:00
|
|
|
nodes, err := s.nodeLister.List()
|
2015-03-24 17:32:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
|
2015-12-06 21:23:56 +00:00
|
|
|
// - Only one protocol supported per service
|
|
|
|
// - Not all cloud providers support all protocols and the next step is expected to return
|
|
|
|
// an error for unsupported protocols
|
2016-05-29 03:54:07 +00:00
|
|
|
status, err := s.balancer.EnsureLoadBalancer(s.clusterName, service, hostsFromNodeList(&nodes))
|
2015-08-21 01:23:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-07-26 05:35:40 +00:00
|
|
|
} else {
|
|
|
|
service.Status.LoadBalancer = *status
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
2015-08-21 01:23:24 +00:00
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-04-09 20:48:27 +00:00
|
|
|
// ListKeys implements the interface required by DeltaFIFO to list the keys we
|
2015-03-24 17:32:43 +00:00
|
|
|
// already know about.
|
2015-04-09 20:48:27 +00:00
|
|
|
func (s *serviceCache) ListKeys() []string {
|
2015-03-24 17:32:43 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
keys := make([]string, 0, len(s.serviceMap))
|
|
|
|
for k := range s.serviceMap {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
2015-05-29 16:24:39 +00:00
|
|
|
// GetByKey returns the value stored in the serviceMap under the given key
|
|
|
|
func (s *serviceCache) GetByKey(key string) (interface{}, bool, error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
if v, ok := s.serviceMap[key]; ok {
|
|
|
|
return v, true, nil
|
|
|
|
}
|
|
|
|
return nil, false, nil
|
|
|
|
}
|
|
|
|
|
2016-07-26 05:35:40 +00:00
|
|
|
// ListKeys implements the interface required by DeltaFIFO to list the keys we
|
|
|
|
// already know about.
|
|
|
|
func (s *serviceCache) allServices() []*api.Service {
|
2015-04-22 20:54:44 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
2016-07-26 05:35:40 +00:00
|
|
|
services := make([]*api.Service, 0, len(s.serviceMap))
|
2015-04-22 20:54:44 +00:00
|
|
|
for _, v := range s.serviceMap {
|
2016-07-26 05:35:40 +00:00
|
|
|
services = append(services, v.state)
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
|
|
|
return services
|
|
|
|
}
|
|
|
|
|
2015-04-09 20:48:27 +00:00
|
|
|
func (s *serviceCache) get(serviceName string) (*cachedService, bool) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
service, ok := s.serviceMap[serviceName]
|
|
|
|
return service, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *serviceCache) getOrCreate(serviceName string) *cachedService {
|
2015-03-24 17:32:43 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
2015-04-09 20:48:27 +00:00
|
|
|
service, ok := s.serviceMap[serviceName]
|
|
|
|
if !ok {
|
|
|
|
service = &cachedService{}
|
|
|
|
s.serviceMap[serviceName] = service
|
|
|
|
}
|
|
|
|
return service
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
2015-04-09 20:48:27 +00:00
|
|
|
func (s *serviceCache) set(serviceName string, service *cachedService) {
|
2015-03-24 17:32:43 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
2015-04-09 20:48:27 +00:00
|
|
|
s.serviceMap[serviceName] = service
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
2015-04-09 20:48:27 +00:00
|
|
|
func (s *serviceCache) delete(serviceName string) {
|
2015-03-24 17:32:43 +00:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
delete(s.serviceMap, serviceName)
|
|
|
|
}
|
|
|
|
|
2016-02-04 00:12:54 +00:00
|
|
|
func (s *ServiceController) needsUpdate(oldService *api.Service, newService *api.Service) bool {
|
2015-09-30 01:42:37 +00:00
|
|
|
if !wantsLoadBalancer(oldService) && !wantsLoadBalancer(newService) {
|
2015-03-24 17:32:43 +00:00
|
|
|
return false
|
|
|
|
}
|
2015-09-30 01:42:37 +00:00
|
|
|
if wantsLoadBalancer(oldService) != wantsLoadBalancer(newService) {
|
2016-02-04 00:12:54 +00:00
|
|
|
s.eventRecorder.Eventf(newService, api.EventTypeNormal, "Type", "%v -> %v",
|
|
|
|
oldService.Spec.Type, newService.Spec.Type)
|
2015-03-24 17:32:43 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-05-22 23:42:04 +00:00
|
|
|
if !portsEqualForLB(oldService, newService) || oldService.Spec.SessionAffinity != newService.Spec.SessionAffinity {
|
2015-03-24 17:32:43 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-08-21 01:23:24 +00:00
|
|
|
if !loadBalancerIPsAreEqual(oldService, newService) {
|
2016-02-04 00:12:54 +00:00
|
|
|
s.eventRecorder.Eventf(newService, api.EventTypeNormal, "LoadbalancerIP", "%v -> %v",
|
|
|
|
oldService.Spec.LoadBalancerIP, newService.Spec.LoadBalancerIP)
|
2015-08-21 01:23:24 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-08-12 00:18:21 +00:00
|
|
|
if len(oldService.Spec.ExternalIPs) != len(newService.Spec.ExternalIPs) {
|
2016-02-04 00:12:54 +00:00
|
|
|
s.eventRecorder.Eventf(newService, api.EventTypeNormal, "ExternalIP", "Count: %v -> %v",
|
|
|
|
len(oldService.Spec.ExternalIPs), len(newService.Spec.ExternalIPs))
|
2015-03-24 17:32:43 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-08-12 00:18:21 +00:00
|
|
|
for i := range oldService.Spec.ExternalIPs {
|
|
|
|
if oldService.Spec.ExternalIPs[i] != newService.Spec.ExternalIPs[i] {
|
2016-02-04 00:12:54 +00:00
|
|
|
s.eventRecorder.Eventf(newService, api.EventTypeNormal, "ExternalIP", "Added: %v",
|
|
|
|
newService.Spec.ExternalIPs[i])
|
2015-03-24 17:32:43 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2016-02-17 22:15:49 +00:00
|
|
|
if !reflect.DeepEqual(oldService.Annotations, newService.Annotations) {
|
|
|
|
return true
|
|
|
|
}
|
2016-02-26 19:38:19 +00:00
|
|
|
if oldService.UID != newService.UID {
|
|
|
|
s.eventRecorder.Eventf(newService, api.EventTypeNormal, "UID", "%v -> %v",
|
|
|
|
oldService.UID, newService.UID)
|
|
|
|
return true
|
|
|
|
}
|
2016-02-17 22:15:49 +00:00
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ServiceController) loadBalancerName(service *api.Service) string {
|
2015-05-03 06:32:21 +00:00
|
|
|
return cloudprovider.GetLoadBalancerName(service)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 23:42:04 +00:00
|
|
|
func getPortsForLB(service *api.Service) ([]*api.ServicePort, error) {
|
2015-09-28 20:57:58 +00:00
|
|
|
var protocol api.Protocol
|
|
|
|
|
2015-05-22 23:42:04 +00:00
|
|
|
ports := []*api.ServicePort{}
|
2015-03-24 17:32:43 +00:00
|
|
|
for i := range service.Spec.Ports {
|
|
|
|
sp := &service.Spec.Ports[i]
|
2015-09-28 20:57:58 +00:00
|
|
|
// The check on protocol was removed here. The cloud provider itself is now responsible for all protocol validation
|
2015-05-22 23:42:04 +00:00
|
|
|
ports = append(ports, sp)
|
2015-09-28 20:57:58 +00:00
|
|
|
if protocol == "" {
|
|
|
|
protocol = sp.Protocol
|
|
|
|
} else if protocol != sp.Protocol && wantsLoadBalancer(service) {
|
2015-12-06 21:23:56 +00:00
|
|
|
// TODO: Convert error messages to use event recorder
|
2015-09-28 20:57:58 +00:00
|
|
|
return nil, fmt.Errorf("mixed protocol external load balancers are not supported.")
|
|
|
|
}
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
return ports, nil
|
|
|
|
}
|
|
|
|
|
2015-05-22 23:42:04 +00:00
|
|
|
func portsEqualForLB(x, y *api.Service) bool {
|
|
|
|
xPorts, err := getPortsForLB(x)
|
2015-03-24 17:32:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2015-05-22 23:42:04 +00:00
|
|
|
yPorts, err := getPortsForLB(y)
|
2015-03-24 17:32:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2015-05-22 23:42:04 +00:00
|
|
|
return portSlicesEqualForLB(xPorts, yPorts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func portSlicesEqualForLB(x, y []*api.ServicePort) bool {
|
|
|
|
if len(x) != len(y) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range x {
|
|
|
|
if !portEqualForLB(x[i], y[i]) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func portEqualForLB(x, y *api.ServicePort) bool {
|
|
|
|
// TODO: Should we check name? (In theory, an LB could expose it)
|
|
|
|
if x.Name != y.Name {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if x.Protocol != y.Protocol {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if x.Port != y.Port {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if x.NodePort != y.NodePort {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't check TargetPort; that is not relevant for load balancing
|
|
|
|
// TODO: Should we blank it out? Or just check it anyway?
|
|
|
|
|
|
|
|
return true
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func intSlicesEqual(x, y []int) bool {
|
|
|
|
if len(x) != len(y) {
|
2015-03-24 17:32:43 +00:00
|
|
|
return false
|
|
|
|
}
|
2015-04-22 20:54:44 +00:00
|
|
|
if !sort.IntsAreSorted(x) {
|
|
|
|
sort.Ints(x)
|
|
|
|
}
|
|
|
|
if !sort.IntsAreSorted(y) {
|
|
|
|
sort.Ints(y)
|
|
|
|
}
|
|
|
|
for i := range x {
|
|
|
|
if x[i] != y[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringSlicesEqual(x, y []string) bool {
|
|
|
|
if len(x) != len(y) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !sort.StringsAreSorted(x) {
|
|
|
|
sort.Strings(x)
|
|
|
|
}
|
|
|
|
if !sort.StringsAreSorted(y) {
|
|
|
|
sort.Strings(y)
|
|
|
|
}
|
|
|
|
for i := range x {
|
|
|
|
if x[i] != y[i] {
|
2015-03-24 17:32:43 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-07-11 14:55:10 +00:00
|
|
|
func includeNodeFromNodeList(node *api.Node) bool {
|
|
|
|
return !node.Spec.Unschedulable
|
|
|
|
}
|
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
func hostsFromNodeList(list *api.NodeList) []string {
|
2015-10-28 17:07:24 +00:00
|
|
|
result := []string{}
|
2015-03-24 17:32:43 +00:00
|
|
|
for ix := range list.Items {
|
2016-07-11 14:55:10 +00:00
|
|
|
if includeNodeFromNodeList(&list.Items[ix]) {
|
|
|
|
result = append(result, list.Items[ix].Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func hostsFromNodeSlice(nodes []*api.Node) []string {
|
|
|
|
result := []string{}
|
|
|
|
for _, node := range nodes {
|
|
|
|
if includeNodeFromNodeList(node) {
|
|
|
|
result = append(result, node.Name)
|
2015-10-28 17:07:24 +00:00
|
|
|
}
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
2015-04-22 20:54:44 +00:00
|
|
|
|
2015-10-28 17:07:24 +00:00
|
|
|
func getNodeConditionPredicate() cache.NodeConditionPredicate {
|
2016-07-07 11:06:32 +00:00
|
|
|
return func(node *api.Node) bool {
|
2015-10-28 17:07:24 +00:00
|
|
|
// We add the master to the node list, but its unschedulable. So we use this to filter
|
|
|
|
// the master.
|
|
|
|
// TODO: Use a node annotation to indicate the master
|
|
|
|
if node.Spec.Unschedulable {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// If we have no info, don't accept
|
|
|
|
if len(node.Status.Conditions) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, cond := range node.Status.Conditions {
|
|
|
|
// We consider the node for load balancing only when its NodeReady condition status
|
|
|
|
// is ConditionTrue
|
|
|
|
if cond.Type == api.NodeReady && cond.Status != api.ConditionTrue {
|
|
|
|
glog.V(4).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-30 01:42:37 +00:00
|
|
|
// nodeSyncLoop handles updating the hosts pointed to by all load
|
2015-04-22 20:54:44 +00:00
|
|
|
// balancers whenever the set of nodes in the cluster changes.
|
2016-07-26 05:35:40 +00:00
|
|
|
func (s *ServiceController) nodeSyncLoop() {
|
|
|
|
nodes, err := s.nodeLister.NodeCondition(getNodeConditionPredicate()).List()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to retrieve current set of nodes from node lister: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newHosts := hostsFromNodeSlice(nodes)
|
|
|
|
if stringSlicesEqual(newHosts, s.knownHosts) {
|
|
|
|
// The set of nodes in the cluster hasn't changed, but we can retry
|
|
|
|
// updating any services that we failed to update last time around.
|
2016-08-23 06:47:50 +00:00
|
|
|
s.servicesToUpdate = s.updateLoadBalancerHosts(s.servicesToUpdate, newHosts)
|
2016-07-26 05:35:40 +00:00
|
|
|
return
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
glog.Infof("Detected change in list of current cluster nodes. New node set: %v", newHosts)
|
|
|
|
|
|
|
|
// Try updating all services, and save the ones that fail to try again next
|
|
|
|
// round.
|
2016-08-23 06:47:50 +00:00
|
|
|
s.servicesToUpdate = s.cache.allServices()
|
|
|
|
numServices := len(s.servicesToUpdate)
|
|
|
|
s.servicesToUpdate = s.updateLoadBalancerHosts(s.servicesToUpdate, newHosts)
|
2016-07-26 05:35:40 +00:00
|
|
|
glog.Infof("Successfully updated %d out of %d load balancers to direct traffic to the updated set of nodes",
|
2016-08-23 06:47:50 +00:00
|
|
|
numServices-len(s.servicesToUpdate), numServices)
|
2016-07-26 05:35:40 +00:00
|
|
|
|
|
|
|
s.knownHosts = newHosts
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
|
|
|
|
2015-09-30 01:42:37 +00:00
|
|
|
// updateLoadBalancerHosts updates all existing load balancers so that
|
2015-04-22 20:54:44 +00:00
|
|
|
// they will match the list of hosts provided.
|
|
|
|
// Returns the list of services that couldn't be updated.
|
2016-07-26 05:35:40 +00:00
|
|
|
func (s *ServiceController) updateLoadBalancerHosts(services []*api.Service, hosts []string) (servicesToRetry []*api.Service) {
|
2015-04-22 20:54:44 +00:00
|
|
|
for _, service := range services {
|
|
|
|
func() {
|
2016-07-26 05:35:40 +00:00
|
|
|
if service == nil {
|
2015-05-18 05:36:48 +00:00
|
|
|
return
|
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
if err := s.lockedUpdateLoadBalancerHosts(service, hosts); err != nil {
|
2015-09-28 20:57:58 +00:00
|
|
|
glog.Errorf("External error while updating load balancer: %v.", err)
|
2015-04-22 20:54:44 +00:00
|
|
|
servicesToRetry = append(servicesToRetry, service)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
return servicesToRetry
|
|
|
|
}
|
|
|
|
|
2015-09-30 01:42:37 +00:00
|
|
|
// Updates the load balancer of a service, assuming we hold the mutex
|
2015-04-22 20:54:44 +00:00
|
|
|
// associated with the service.
|
|
|
|
func (s *ServiceController) lockedUpdateLoadBalancerHosts(service *api.Service, hosts []string) error {
|
2015-09-30 01:42:37 +00:00
|
|
|
if !wantsLoadBalancer(service) {
|
2015-04-22 20:54:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-13 15:39:52 +00:00
|
|
|
// This operation doesn't normally take very long (and happens pretty often), so we only record the final event
|
2016-05-29 03:54:07 +00:00
|
|
|
err := s.balancer.UpdateLoadBalancer(s.clusterName, service, hosts)
|
2015-04-22 20:54:44 +00:00
|
|
|
if err == nil {
|
2015-11-13 22:30:01 +00:00
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "UpdatedLoadBalancer", "Updated load balancer with new hosts")
|
2015-04-22 20:54:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's only an actual error if the load balancer still exists.
|
2016-05-29 03:54:07 +00:00
|
|
|
if _, exists, err := s.balancer.GetLoadBalancer(s.clusterName, service); err != nil {
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
glog.Errorf("External error while checking if load balancer %q exists: name, %v", cloudprovider.GetLoadBalancerName(service), err)
|
2015-04-22 20:54:44 +00:00
|
|
|
} else if !exists {
|
|
|
|
return nil
|
|
|
|
}
|
2015-06-13 15:39:52 +00:00
|
|
|
|
2015-11-13 22:30:01 +00:00
|
|
|
s.eventRecorder.Eventf(service, api.EventTypeWarning, "LoadBalancerUpdateFailed", "Error updating load balancer with new hosts %v: %v", hosts, err)
|
2015-04-22 20:54:44 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-05-22 21:49:26 +00:00
|
|
|
|
2015-09-30 01:42:37 +00:00
|
|
|
func wantsLoadBalancer(service *api.Service) bool {
|
2015-05-22 21:49:26 +00:00
|
|
|
return service.Spec.Type == api.ServiceTypeLoadBalancer
|
|
|
|
}
|
2015-08-21 01:23:24 +00:00
|
|
|
|
|
|
|
func loadBalancerIPsAreEqual(oldService, newService *api.Service) bool {
|
|
|
|
return oldService.Spec.LoadBalancerIP == newService.Spec.LoadBalancerIP
|
|
|
|
}
|
2016-02-25 14:51:11 +00:00
|
|
|
|
|
|
|
// Computes the next retry, using exponential backoff
|
|
|
|
// mutex must be held.
|
|
|
|
func (s *cachedService) nextRetryDelay() time.Duration {
|
|
|
|
s.lastRetryDelay = s.lastRetryDelay * 2
|
|
|
|
if s.lastRetryDelay < minRetryDelay {
|
|
|
|
s.lastRetryDelay = minRetryDelay
|
|
|
|
}
|
|
|
|
if s.lastRetryDelay > maxRetryDelay {
|
|
|
|
s.lastRetryDelay = maxRetryDelay
|
|
|
|
}
|
|
|
|
return s.lastRetryDelay
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resets the retry exponential backoff. mutex must be held.
|
|
|
|
func (s *cachedService) resetRetryDelay() {
|
|
|
|
s.lastRetryDelay = time.Duration(0)
|
|
|
|
}
|
2016-07-26 05:35:40 +00:00
|
|
|
|
|
|
|
// syncService will sync the Service with the given key if it has had its expectations fulfilled,
|
|
|
|
// meaning it did not expect to see any more of its pods created or deleted. This function is not meant to be
|
|
|
|
// invoked concurrently with the same key.
|
|
|
|
func (s *ServiceController) syncService(key string) error {
|
|
|
|
startTime := time.Now()
|
|
|
|
var cachedService *cachedService
|
|
|
|
var retryDelay time.Duration
|
|
|
|
defer func() {
|
|
|
|
glog.V(4).Infof("Finished syncing service %q (%v)", key, time.Now().Sub(startTime))
|
|
|
|
}()
|
|
|
|
// obj holds the latest service info from apiserver
|
2016-09-16 17:38:50 +00:00
|
|
|
obj, exists, err := s.serviceStore.Indexer.GetByKey(key)
|
2016-07-26 05:35:40 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Infof("Unable to retrieve service %v from store: %v", key, err)
|
|
|
|
s.workingQueue.Add(key)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
// service absence in store means watcher caught the deletion, ensure LB info is cleaned
|
|
|
|
glog.Infof("Service has been deleted %v", key)
|
|
|
|
err, retryDelay = s.processServiceDeletion(key)
|
|
|
|
} else {
|
|
|
|
service, ok := obj.(*api.Service)
|
|
|
|
if ok {
|
|
|
|
cachedService = s.cache.getOrCreate(key)
|
|
|
|
err, retryDelay = s.processServiceUpdate(cachedService, service, key)
|
|
|
|
} else {
|
|
|
|
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("object contained wasn't a service or a deleted key: %#v", obj)
|
|
|
|
}
|
|
|
|
glog.Infof("Found tombstone for %v", key)
|
|
|
|
err, retryDelay = s.processServiceDeletion(tombstone.Key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if retryDelay != 0 {
|
|
|
|
// Add the failed service back to the queue so we'll retry it.
|
|
|
|
glog.Errorf("Failed to process service. Retrying in %s: %v", retryDelay, err)
|
|
|
|
go func(obj interface{}, delay time.Duration) {
|
|
|
|
// put back the service key to working queue, it is possible that more entries of the service
|
|
|
|
// were added into the queue during the delay, but it does not mess as when handling the retry,
|
|
|
|
// it always get the last service info from service store
|
|
|
|
s.workingQueue.AddAfter(obj, delay)
|
|
|
|
}(key, retryDelay)
|
|
|
|
} else if err != nil {
|
|
|
|
runtime.HandleError(fmt.Errorf("Failed to process service. Not retrying: %v", err))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns an error if processing the service deletion failed, along with a time.Duration
|
|
|
|
// indicating whether processing should be retried; zero means no-retry; otherwise
|
|
|
|
// we should retry after that Duration.
|
|
|
|
func (s *ServiceController) processServiceDeletion(key string) (error, time.Duration) {
|
|
|
|
cachedService, ok := s.cache.get(key)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Service %s not in cache even though the watcher thought it was. Ignoring the deletion.", key), doNotRetry
|
|
|
|
}
|
|
|
|
service := cachedService.state
|
|
|
|
// delete load balancer info only if the service type is LoadBalancer
|
|
|
|
if !wantsLoadBalancer(service) {
|
|
|
|
return nil, doNotRetry
|
|
|
|
}
|
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "DeletingLoadBalancer", "Deleting load balancer")
|
|
|
|
err := s.balancer.EnsureLoadBalancerDeleted(s.clusterName, service)
|
|
|
|
if err != nil {
|
|
|
|
message := "Error deleting load balancer (will retry): " + err.Error()
|
|
|
|
s.eventRecorder.Event(service, api.EventTypeWarning, "DeletingLoadBalancerFailed", message)
|
|
|
|
return err, cachedService.nextRetryDelay()
|
|
|
|
}
|
|
|
|
s.eventRecorder.Event(service, api.EventTypeNormal, "DeletedLoadBalancer", "Deleted load balancer")
|
|
|
|
s.cache.delete(key)
|
|
|
|
|
|
|
|
cachedService.resetRetryDelay()
|
|
|
|
return nil, doNotRetry
|
|
|
|
}
|