Change CreateTCPLoadBalancer -> EnsureTCPLoadBalancer; implementations auto-delete if already exists

Previously the servicecontroller would do the delete, but by having the cloudprovider
take that task on, we can later remove it from the servicecontroller, and the
cloudprovider can do something more efficient.
This commit is contained in:
Justin Santa Barbara 2015-06-13 11:58:39 -04:00
parent 1ab62e541b
commit 87df1d6fb6
7 changed files with 80 additions and 22 deletions

View File

@ -80,8 +80,8 @@ type TCPLoadBalancer interface {
// GetTCPLoadBalancer returns whether the specified load balancer exists, and // GetTCPLoadBalancer returns whether the specified load balancer exists, and
// if so, what its status is. // if so, what its status is.
GetTCPLoadBalancer(name, region string) (status *api.LoadBalancerStatus, exists bool, err error) GetTCPLoadBalancer(name, region string) (status *api.LoadBalancerStatus, exists bool, err error)
// CreateTCPLoadBalancer creates a new tcp load balancer. Returns the status of the balancer // EnsureTCPLoadBalancer creates a new tcp load balancer, or updates an existing one. Returns the status of the balancer
CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error)
// UpdateTCPLoadBalancer updates hosts under the specified load balancer. // UpdateTCPLoadBalancer updates hosts under the specified load balancer.
UpdateTCPLoadBalancer(name, region string, hosts []string) error UpdateTCPLoadBalancer(name, region string, hosts []string) error
// EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it // EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it

View File

@ -1584,8 +1584,23 @@ func (s *AWSCloud) createTags(request *ec2.CreateTagsInput) (*ec2.CreateTagsOutp
// CreateTCPLoadBalancer implements TCPLoadBalancer.CreateTCPLoadBalancer // CreateTCPLoadBalancer implements TCPLoadBalancer.CreateTCPLoadBalancer
// TODO(justinsb): This must be idempotent // TODO(justinsb): This must be idempotent
// TODO(justinsb) It is weird that these take a region. I suspect it won't work cross-region anwyay. // TODO(justinsb) It is weird that these take a region. I suspect it won't work cross-region anwyay.
func (s *AWSCloud) CreateTCPLoadBalancer(name, region string, publicIP net.IP, ports []*api.ServicePort, hosts []string, affinity api.ServiceAffinity) (*api.LoadBalancerStatus, error) { func (s *AWSCloud) EnsureTCPLoadBalancer(name, region string, publicIP net.IP, ports []*api.ServicePort, hosts []string, affinity api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
glog.V(2).Infof("CreateTCPLoadBalancer(%v, %v, %v, %v, %v)", name, region, publicIP, ports, hosts) glog.V(2).Infof("EnsureTCPLoadBalancer(%v, %v, %v, %v, %v)", name, region, publicIP, ports, hosts)
glog.V(2).Info("Checking if load balancer already exists: %s", name)
_, exists, err := s.GetTCPLoadBalancer(name, region)
if err != nil {
return nil, fmt.Errorf("error checking if AWS load balancer already exists: %v", err)
}
// TODO: Implement a more efficient update strategy for common changes than delete & create
// In particular, if we implement hosts update, we can get rid of UpdateHosts
if exists {
err := s.EnsureTCPLoadBalancerDeleted(name, region)
if err != nil {
return nil, fmt.Errorf("error deleting existing AWS load balancer: %v", err)
}
}
elbClient, err := s.getELBClient(region) elbClient, err := s.getELBClient(region)
if err != nil { if err != nil {

View File

@ -56,7 +56,7 @@ type FakeCloud struct {
ClusterList []string ClusterList []string
MasterName string MasterName string
ExternalIP net.IP ExternalIP net.IP
Balancers []FakeBalancer Balancers map[string]FakeBalancer
UpdateCalls []FakeUpdateBalancerCall UpdateCalls []FakeUpdateBalancerCall
RouteMap map[string]*FakeRoute RouteMap map[string]*FakeRoute
Lock sync.Mutex Lock sync.Mutex
@ -123,11 +123,14 @@ func (f *FakeCloud) GetTCPLoadBalancer(name, region string) (*api.LoadBalancerSt
return status, f.Exists, f.Err return status, f.Exists, f.Err
} }
// CreateTCPLoadBalancer is a test-spy implementation of TCPLoadBalancer.CreateTCPLoadBalancer. // EnsureTCPLoadBalancer is a test-spy implementation of TCPLoadBalancer.EnsureTCPLoadBalancer.
// It adds an entry "create" into the internal method call record. // It adds an entry "create" into the internal method call record.
func (f *FakeCloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) { func (f *FakeCloud) EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
f.addCall("create") f.addCall("create")
f.Balancers = append(f.Balancers, FakeBalancer{name, region, externalIP, ports, hosts}) if f.Balancers == nil {
f.Balancers = make(map[string]FakeBalancer)
}
f.Balancers[name] = FakeBalancer{name, region, externalIP, ports, hosts}
status := &api.LoadBalancerStatus{} status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}} status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}}

View File

@ -348,11 +348,26 @@ func makeFirewallName(name string) string {
return fmt.Sprintf("k8s-fw-%s", name) return fmt.Sprintf("k8s-fw-%s", name)
} }
// CreateTCPLoadBalancer is an implementation of TCPLoadBalancer.CreateTCPLoadBalancer. // EnsureTCPLoadBalancer is an implementation of TCPLoadBalancer.EnsureTCPLoadBalancer.
// TODO(a-robinson): Don't just ignore specified IP addresses. Check if they're // TODO(a-robinson): Don't just ignore specified IP addresses. Check if they're
// owned by the project and available to be used, and use them if they are. // owned by the project and available to be used, and use them if they are.
func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) { func (gce *GCECloud) EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
err := gce.makeTargetPool(name, region, hosts, translateAffinityType(affinityType)) glog.V(2).Info("Checking if load balancer already exists: %s", name)
_, exists, err := gce.GetTCPLoadBalancer(name, region)
if err != nil {
return nil, fmt.Errorf("error checking if GCE load balancer already exists: %v", err)
}
// TODO: Implement a more efficient update strategy for common changes than delete & create
// In particular, if we implement hosts update, we can get rid of UpdateHosts
if exists {
err := gce.EnsureTCPLoadBalancerDeleted(name, region)
if err != nil {
return nil, fmt.Errorf("error deleting existing GCE load balancer: %v", err)
}
}
err = gce.makeTargetPool(name, region, hosts, translateAffinityType(affinityType))
if err != nil { if err != nil {
if !isHTTPErrorCode(err, http.StatusConflict) { if !isHTTPErrorCode(err, http.StatusConflict) {
return nil, err return nil, err

View File

@ -521,8 +521,8 @@ func (lb *LoadBalancer) GetTCPLoadBalancer(name, region string) (*api.LoadBalanc
// a list of regions (from config) and query/create loadbalancers in // a list of regions (from config) and query/create loadbalancers in
// each region. // each region.
func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinity api.ServiceAffinity) (*api.LoadBalancerStatus, error) { func (lb *LoadBalancer) EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinity api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
glog.V(4).Infof("CreateTCPLoadBalancer(%v, %v, %v, %v, %v, %v)", name, region, externalIP, ports, hosts, affinity) glog.V(4).Infof("EnsureTCPLoadBalancer(%v, %v, %v, %v, %v, %v)", name, region, externalIP, ports, hosts, affinity)
if len(ports) > 1 { if len(ports) > 1 {
return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers") return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers")
@ -538,6 +538,21 @@ func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP ne
return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity) return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity)
} }
glog.V(2).Info("Checking if openstack load balancer already exists: %s", name)
_, exists, err := lb.GetTCPLoadBalancer(name, region)
if err != nil {
return nil, fmt.Errorf("error checking if openstack load balancer already exists: %v", err)
}
// TODO: Implement a more efficient update strategy for common changes than delete & create
// In particular, if we implement hosts update, we can get rid of UpdateHosts
if exists {
err := lb.EnsureTCPLoadBalancerDeleted(name, region)
if err != nil {
return nil, fmt.Errorf("error deleting existing openstack load balancer: %v", err)
}
}
lbmethod := lb.opts.LBMethod lbmethod := lb.opts.LBMethod
if lbmethod == "" { if lbmethod == "" {
lbmethod = pools.LBMethodRoundRobin lbmethod = pools.LBMethodRoundRobin

View File

@ -335,7 +335,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.Name
return fmt.Errorf("Failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable return fmt.Errorf("Failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable
} }
} else { } else {
glog.Infof("Not persisting unchanged LoadBalancerStatus to registry.") glog.V(2).Infof("Not persisting unchanged LoadBalancerStatus to registry.")
} }
return nil, notRetryable return nil, notRetryable
@ -383,7 +383,7 @@ func (s *ServiceController) createExternalLoadBalancer(service *api.Service) err
for _, publicIP := range service.Spec.DeprecatedPublicIPs { for _, publicIP := range service.Spec.DeprecatedPublicIPs {
// TODO: Make this actually work for multiple IPs by using different // TODO: Make this actually work for multiple IPs by using different
// names for each. For now, we'll just create the first and break. // names for each. For now, we'll just create the first and break.
status, err := s.balancer.CreateTCPLoadBalancer(name, s.zone.Region, net.ParseIP(publicIP), status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, net.ParseIP(publicIP),
ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity) ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
if err != nil { if err != nil {
return err return err
@ -393,7 +393,7 @@ func (s *ServiceController) createExternalLoadBalancer(service *api.Service) err
break break
} }
} else { } else {
status, err := s.balancer.CreateTCPLoadBalancer(name, s.zone.Region, nil, status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, nil,
ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity) ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
if err != nil { if err != nil {
return err return err

View File

@ -110,12 +110,22 @@ func TestCreateExternalLoadBalancer(t *testing.T) {
t.Errorf("unexpected client actions: %v", actions) t.Errorf("unexpected client actions: %v", actions)
} }
} else { } else {
if len(cloud.Balancers) != 1 { var balancer *fake_cloud.FakeBalancer
for k := range cloud.Balancers {
if balancer == nil {
b := cloud.Balancers[k]
balancer = &b
} else {
t.Errorf("expected one load balancer to be created, got %v", cloud.Balancers) t.Errorf("expected one load balancer to be created, got %v", cloud.Balancers)
} else if cloud.Balancers[0].Name != controller.loadBalancerName(item.service) || break
cloud.Balancers[0].Region != region || }
cloud.Balancers[0].Ports[0].Port != item.service.Spec.Ports[0].Port { }
t.Errorf("created load balancer has incorrect parameters: %v", cloud.Balancers[0]) if balancer == nil {
t.Errorf("expected one load balancer to be created, got none")
} else if balancer.Name != controller.loadBalancerName(item.service) ||
balancer.Region != region ||
balancer.Ports[0].Port != item.service.Spec.Ports[0].Port {
t.Errorf("created load balancer has incorrect parameters: %v", balancer)
} }
actionFound := false actionFound := false
for _, action := range actions { for _, action := range actions {