Add LoadBalancer status to ServiceStatus

This will replace publicIPs
This commit is contained in:
Justin Santa Barbara 2015-05-22 17:33:29 -04:00
parent 1ad4549f5f
commit 3884d5fc59
21 changed files with 500 additions and 95 deletions

View File

@ -141,3 +141,40 @@ func HashObject(obj runtime.Object, codec runtime.Codec) (string, error) {
}
return fmt.Sprintf("%x", md5.Sum(data)), nil
}
// TODO: make method on LoadBalancerStatus?
func LoadBalancerStatusEqual(l, r *LoadBalancerStatus) bool {
return ingressSliceEqual(l.Ingress, r.Ingress)
}
func ingressSliceEqual(lhs, rhs []LoadBalancerIngress) bool {
if len(lhs) != len(rhs) {
return false
}
for i := range lhs {
if !ingressEqual(&lhs[i], &rhs[i]) {
return false
}
}
return true
}
func ingressEqual(lhs, rhs *LoadBalancerIngress) bool {
if lhs.IP != rhs.IP {
return false
}
if lhs.Hostname != rhs.Hostname {
return false
}
return true
}
// TODO: make method on LoadBalancerStatus?
func LoadBalancerStatusDeepCopy(lb *LoadBalancerStatus) *LoadBalancerStatus {
c := &LoadBalancerStatus{}
c.Ingress = make([]LoadBalancerIngress, len(lb.Ingress))
for i := range lb.Ingress {
c.Ingress[i] = lb.Ingress[i]
}
return c
}

View File

@ -1012,7 +1012,30 @@ const (
)
// ServiceStatus represents the current status of a service
type ServiceStatus struct{}
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty" description:"load-balancer ingress points"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty" description:"IP address of ingress point"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty" description:"hostname of ingress point"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {

View File

@ -812,6 +812,32 @@ func convert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *ListOpt
return nil
}
func convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *LoadBalancerIngress, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.LoadBalancerIngress))(in)
}
out.IP = in.IP
out.Hostname = in.Hostname
return nil
}
func convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *LoadBalancerStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.LoadBalancerStatus))(in)
}
if in.Ingress != nil {
out.Ingress = make([]LoadBalancerIngress, len(in.Ingress))
for i := range in.Ingress {
if err := convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
return err
}
}
} else {
out.Ingress = nil
}
return nil
}
func convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *LocalObjectReference, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.LocalObjectReference))(in)
@ -2088,6 +2114,9 @@ func convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *S
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.ServiceStatus))(in)
}
if err := convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil {
return err
}
return nil
}
@ -3038,6 +3067,32 @@ func convert_v1_ListOptions_To_api_ListOptions(in *ListOptions, out *api.ListOpt
return nil
}
func convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in *LoadBalancerIngress, out *api.LoadBalancerIngress, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*LoadBalancerIngress))(in)
}
out.IP = in.IP
out.Hostname = in.Hostname
return nil
}
func convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in *LoadBalancerStatus, out *api.LoadBalancerStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*LoadBalancerStatus))(in)
}
if in.Ingress != nil {
out.Ingress = make([]api.LoadBalancerIngress, len(in.Ingress))
for i := range in.Ingress {
if err := convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
return err
}
}
} else {
out.Ingress = nil
}
return nil
}
func convert_v1_LocalObjectReference_To_api_LocalObjectReference(in *LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*LocalObjectReference))(in)
@ -4314,6 +4369,9 @@ func convert_v1_ServiceStatus_To_api_ServiceStatus(in *ServiceStatus, out *api.S
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ServiceStatus))(in)
}
if err := convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil {
return err
}
return nil
}
@ -4520,6 +4578,8 @@ func init() {
convert_api_ListMeta_To_v1_ListMeta,
convert_api_ListOptions_To_v1_ListOptions,
convert_api_List_To_v1_List,
convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress,
convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus,
convert_api_LocalObjectReference_To_v1_LocalObjectReference,
convert_api_NFSVolumeSource_To_v1_NFSVolumeSource,
convert_api_NamespaceList_To_v1_NamespaceList,
@ -4629,6 +4689,8 @@ func init() {
convert_v1_ListMeta_To_api_ListMeta,
convert_v1_ListOptions_To_api_ListOptions,
convert_v1_List_To_api_List,
convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress,
convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus,
convert_v1_LocalObjectReference_To_api_LocalObjectReference,
convert_v1_NFSVolumeSource_To_api_NFSVolumeSource,
convert_v1_NamespaceList_To_api_NamespaceList,

View File

@ -994,7 +994,30 @@ const (
)
// ServiceStatus represents the current status of a service
type ServiceStatus struct{}
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty" description:"status of load-balancer"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty" description:"load-balancer ingress points"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty" description:"IP address of ingress point"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty" description:"hostname of ingress point"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {

View File

@ -787,6 +787,10 @@ func addConversionFuncs() {
return err
}
if err := s.Convert(&in.Status.LoadBalancer, &out.LoadBalancerStatus, 0); err != nil {
return err
}
return nil
},
func(in *Service, out *api.Service, s conversion.Scope) error {
@ -830,6 +834,22 @@ func addConversionFuncs() {
return err
}
if err := s.Convert(&in.LoadBalancerStatus, &out.Status.LoadBalancer, 0); err != nil {
return err
}
typeIn := in.Type
if typeIn == "" {
if in.CreateExternalLoadBalancer {
typeIn = ServiceTypeLoadBalancer
} else {
typeIn = ServiceTypeClusterIP
}
}
if err := s.Convert(&typeIn, &out.Spec.Type, 0); err != nil {
return err
}
return nil
},

View File

@ -896,6 +896,29 @@ type Service struct {
// array. If this field is not specified, it will be populated from
// the legacy fields.
Ports []ServicePort `json:"ports" description:"ports to be exposed on the service; if this field is specified, the legacy fields (Port, PortName, Protocol, and ContainerPort) will be overwritten by the first member of this array; if this field is not specified, it will be populated from the legacy fields"`
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancerStatus LoadBalancerStatus `json:"loadBalancerStatus,omitempty" description:"status of load-balancer"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty" description:"load-balancer ingress points"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty" description:"IP address of ingress point"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty" description:"hostname of ingress point"`
}
type ServicePort struct {

View File

@ -709,6 +709,10 @@ func addConversionFuncs() {
return err
}
if err := s.Convert(&in.Status.LoadBalancer, &out.LoadBalancerStatus, 0); err != nil {
return err
}
return nil
},
func(in *Service, out *api.Service, s conversion.Scope) error {
@ -752,6 +756,22 @@ func addConversionFuncs() {
return err
}
if err := s.Convert(&in.LoadBalancerStatus, &out.Status.LoadBalancer, 0); err != nil {
return err
}
typeIn := in.Type
if typeIn == "" {
if in.CreateExternalLoadBalancer {
typeIn = ServiceTypeLoadBalancer
} else {
typeIn = ServiceTypeClusterIP
}
}
if err := s.Convert(&typeIn, &out.Spec.Type, 0); err != nil {
return err
}
return nil
},

View File

@ -900,6 +900,29 @@ type Service struct {
// array. If this field is not specified, it will be populated from
// the legacy fields.
Ports []ServicePort `json:"ports" description:"ports to be exposed on the service; if this field is specified, the legacy fields (Port, PortName, Protocol, and ContainerPort) will be overwritten by the first member of this array; if this field is not specified, it will be populated from the legacy fields"`
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancerStatus LoadBalancerStatus `json:"loadBalancerStatus,omitempty" description:"status of load-balancer"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty" description:"load-balancer ingress points"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty" description:"IP address of ingress point"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty" description:"hostname of ingress point"`
}
type ServicePort struct {

View File

@ -719,6 +719,32 @@ func convert_api_ListOptions_To_v1beta3_ListOptions(in *api.ListOptions, out *Li
return nil
}
func convert_api_LoadBalancerIngress_To_v1beta3_LoadBalancerIngress(in *api.LoadBalancerIngress, out *LoadBalancerIngress, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.LoadBalancerIngress))(in)
}
out.IP = in.IP
out.Hostname = in.Hostname
return nil
}
func convert_api_LoadBalancerStatus_To_v1beta3_LoadBalancerStatus(in *api.LoadBalancerStatus, out *LoadBalancerStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.LoadBalancerStatus))(in)
}
if in.Ingress != nil {
out.Ingress = make([]LoadBalancerIngress, len(in.Ingress))
for i := range in.Ingress {
if err := convert_api_LoadBalancerIngress_To_v1beta3_LoadBalancerIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
return err
}
}
} else {
out.Ingress = nil
}
return nil
}
func convert_api_LocalObjectReference_To_v1beta3_LocalObjectReference(in *api.LocalObjectReference, out *LocalObjectReference, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.LocalObjectReference))(in)
@ -2027,6 +2053,9 @@ func convert_api_ServiceStatus_To_v1beta3_ServiceStatus(in *api.ServiceStatus, o
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.ServiceStatus))(in)
}
if err := convert_api_LoadBalancerStatus_To_v1beta3_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil {
return err
}
return nil
}
@ -2914,6 +2943,32 @@ func convert_v1beta3_ListOptions_To_api_ListOptions(in *ListOptions, out *api.Li
return nil
}
func convert_v1beta3_LoadBalancerIngress_To_api_LoadBalancerIngress(in *LoadBalancerIngress, out *api.LoadBalancerIngress, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*LoadBalancerIngress))(in)
}
out.IP = in.IP
out.Hostname = in.Hostname
return nil
}
func convert_v1beta3_LoadBalancerStatus_To_api_LoadBalancerStatus(in *LoadBalancerStatus, out *api.LoadBalancerStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*LoadBalancerStatus))(in)
}
if in.Ingress != nil {
out.Ingress = make([]api.LoadBalancerIngress, len(in.Ingress))
for i := range in.Ingress {
if err := convert_v1beta3_LoadBalancerIngress_To_api_LoadBalancerIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil {
return err
}
}
} else {
out.Ingress = nil
}
return nil
}
func convert_v1beta3_LocalObjectReference_To_api_LocalObjectReference(in *LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*LocalObjectReference))(in)
@ -4222,6 +4277,9 @@ func convert_v1beta3_ServiceStatus_To_api_ServiceStatus(in *ServiceStatus, out *
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ServiceStatus))(in)
}
if err := convert_v1beta3_LoadBalancerStatus_To_api_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil {
return err
}
return nil
}
@ -4457,6 +4515,8 @@ func init() {
convert_api_ListMeta_To_v1beta3_ListMeta,
convert_api_ListOptions_To_v1beta3_ListOptions,
convert_api_List_To_v1beta3_List,
convert_api_LoadBalancerIngress_To_v1beta3_LoadBalancerIngress,
convert_api_LoadBalancerStatus_To_v1beta3_LoadBalancerStatus,
convert_api_LocalObjectReference_To_v1beta3_LocalObjectReference,
convert_api_NFSVolumeSource_To_v1beta3_NFSVolumeSource,
convert_api_NamespaceList_To_v1beta3_NamespaceList,
@ -4568,6 +4628,8 @@ func init() {
convert_v1beta3_ListMeta_To_api_ListMeta,
convert_v1beta3_ListOptions_To_api_ListOptions,
convert_v1beta3_List_To_api_List,
convert_v1beta3_LoadBalancerIngress_To_api_LoadBalancerIngress,
convert_v1beta3_LoadBalancerStatus_To_api_LoadBalancerStatus,
convert_v1beta3_LocalObjectReference_To_api_LocalObjectReference,
convert_v1beta3_NFSVolumeSource_To_api_NFSVolumeSource,
convert_v1beta3_NamespaceList_To_api_NamespaceList,

View File

@ -998,7 +998,30 @@ const (
)
// ServiceStatus represents the current status of a service
type ServiceStatus struct{}
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty" description:"status of load-balancer"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty" description:"load-balancer ingress points"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty" description:"IP address of ingress point"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty" description:"hostname of ingress point"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {

View File

@ -63,10 +63,10 @@ func GetLoadBalancerName(service *api.Service) string {
type TCPLoadBalancer interface {
// TODO: Break this up into different interfaces (LB, etc) when we have more than one type of service
// GetTCPLoadBalancer returns whether the specified load balancer exists, and
// if so, what its IP address or hostname is.
GetTCPLoadBalancer(name, region string) (endpoint string, exists bool, err error)
// CreateTCPLoadBalancer creates a new tcp load balancer. Returns the IP address or hostname of the balancer
CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (string, error)
// if so, what its status is.
GetTCPLoadBalancer(name, region string) (status *api.LoadBalancerStatus, exists bool, err error)
// CreateTCPLoadBalancer creates a new tcp load balancer. Returns the status of the balancer
CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error)
// UpdateTCPLoadBalancer updates hosts under the specified load balancer.
UpdateTCPLoadBalancer(name, region string, hosts []string) error
// EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it

View File

@ -103,16 +103,23 @@ func (f *FakeCloud) Routes() (cloudprovider.Routes, bool) {
}
// GetTCPLoadBalancer is a stub implementation of TCPLoadBalancer.GetTCPLoadBalancer.
func (f *FakeCloud) GetTCPLoadBalancer(name, region string) (endpoint string, exists bool, err error) {
return f.ExternalIP.String(), f.Exists, f.Err
func (f *FakeCloud) GetTCPLoadBalancer(name, region string) (*api.LoadBalancerStatus, bool, error) {
status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}}
return status, f.Exists, f.Err
}
// CreateTCPLoadBalancer is a test-spy implementation of TCPLoadBalancer.CreateTCPLoadBalancer.
// It adds an entry "create" into the internal method call record.
func (f *FakeCloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (string, error) {
func (f *FakeCloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
f.addCall("create")
f.Balancers = append(f.Balancers, FakeBalancer{name, region, externalIP, ports, hosts})
return f.ExternalIP.String(), f.Err
status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}}
return status, f.Err
}
// UpdateTCPLoadBalancer is a test-spy implementation of TCPLoadBalancer.UpdateTCPLoadBalancer.

View File

@ -282,15 +282,18 @@ func (gce *GCECloud) waitForZoneOp(op *compute.Operation) error {
}
// GetTCPLoadBalancer is an implementation of TCPLoadBalancer.GetTCPLoadBalancer
func (gce *GCECloud) GetTCPLoadBalancer(name, region string) (endpoint string, exists bool, err error) {
fw, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do()
func (gce *GCECloud) GetTCPLoadBalancer(name, region string) (*api.LoadBalancerStatus, bool, error) {
fwd, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do()
if err == nil {
return fw.IPAddress, true, nil
status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: fwd.IPAddress}}
return status, true, nil
}
if isHTTPErrorCode(err, http.StatusNotFound) {
return "", false, nil
return nil, false, nil
}
return "", false, err
return nil, false, err
}
func isHTTPErrorCode(err error, code int) bool {
@ -314,17 +317,17 @@ func translateAffinityType(affinityType api.ServiceAffinity) GCEAffinityType {
// CreateTCPLoadBalancer is an implementation of TCPLoadBalancer.CreateTCPLoadBalancer.
// 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.
func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (string, error) {
func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
err := gce.makeTargetPool(name, region, hosts, translateAffinityType(affinityType))
if err != nil {
if !isHTTPErrorCode(err, http.StatusConflict) {
return "", err
return nil, err
}
glog.Infof("Creating forwarding rule pointing at target pool that already exists: %v", err)
}
if len(ports) == 0 {
return "", fmt.Errorf("no ports specified for GCE load balancer")
return nil, fmt.Errorf("no ports specified for GCE load balancer")
}
minPort := 65536
maxPort := 0
@ -344,19 +347,22 @@ func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, externalIP net.I
}
op, err := gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do()
if err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
return "", err
return nil, err
}
if op != nil {
err = gce.waitForRegionOp(op, region)
if err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
return "", err
return nil, err
}
}
fwd, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do()
if err != nil {
return "", err
return nil, err
}
return fwd.IPAddress, nil
status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: fwd.IPAddress}}
return status, nil
}
// UpdateTCPLoadBalancer is an implementation of TCPLoadBalancer.UpdateTCPLoadBalancer.

View File

@ -457,15 +457,19 @@ func getVipByName(client *gophercloud.ServiceClient, name string) (*vips.Virtual
return &vipList[0], nil
}
func (lb *LoadBalancer) GetTCPLoadBalancer(name, region string) (endpoint string, exists bool, err error) {
func (lb *LoadBalancer) GetTCPLoadBalancer(name, region string) (*api.LoadBalancerStatus, bool, error) {
vip, err := getVipByName(lb.network, name)
if err == ErrNotFound {
return "", false, nil
return nil, false, nil
}
if vip == nil {
return "", false, err
return nil, false, err
}
return vip.Address, true, err
status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: vip.Address}}
return status, true, err
}
// TODO: This code currently ignores 'region' and always creates a
@ -473,11 +477,11 @@ func (lb *LoadBalancer) GetTCPLoadBalancer(name, region string) (endpoint string
// a list of regions (from config) and query/create loadbalancers in
// each region.
func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinity api.ServiceAffinity) (string, error) {
func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, 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)
if len(ports) > 1 {
return "", 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")
}
var persistence *vips.SessionPersistence
@ -487,7 +491,7 @@ func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP ne
case api.ServiceAffinityClientIP:
persistence = &vips.SessionPersistence{Type: "SOURCE_IP"}
default:
return "", fmt.Errorf("unsupported load balancer affinity: %v", affinity)
return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity)
}
lbmethod := lb.opts.LBMethod
@ -501,13 +505,13 @@ func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP ne
LBMethod: lbmethod,
}).Extract()
if err != nil {
return "", err
return nil, err
}
for _, host := range hosts {
addr, err := getAddressByName(lb.compute, host)
if err != nil {
return "", err
return nil, err
}
_, err = members.Create(lb.network, members.CreateOpts{
@ -517,7 +521,7 @@ func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP ne
}).Extract()
if err != nil {
pools.Delete(lb.network, pool.ID)
return "", err
return nil, err
}
}
@ -531,14 +535,14 @@ func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP ne
}).Extract()
if err != nil {
pools.Delete(lb.network, pool.ID)
return "", err
return nil, err
}
_, err = pools.AssociateMonitor(lb.network, pool.ID, mon.ID).Extract()
if err != nil {
monitors.Delete(lb.network, mon.ID)
pools.Delete(lb.network, pool.ID)
return "", err
return nil, err
}
}
@ -556,10 +560,13 @@ func (lb *LoadBalancer) CreateTCPLoadBalancer(name, region string, externalIP ne
monitors.Delete(lb.network, mon.ID)
}
pools.Delete(lb.network, pool.ID)
return "", err
return nil, err
}
return vip.Address, nil
status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: vip.Address}}
return status, nil
}
func (lb *LoadBalancer) UpdateTCPLoadBalancer(name, region string, hosts []string) error {

View File

@ -240,20 +240,16 @@ func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.Name
} else {
// If we don't have any cached memory of the load balancer, we have to ask
// the cloud provider for what it knows about it.
endpoint, exists, err := s.balancer.GetTCPLoadBalancer(s.loadBalancerName(service), s.zone.Region)
status, exists, err := s.balancer.GetTCPLoadBalancer(s.loadBalancerName(service), s.zone.Region)
if err != nil {
return fmt.Errorf("Error getting LB for service %s", namespacedName), retryable
}
if exists && stringSlicesEqual(service.Spec.PublicIPs, []string{endpoint}) {
// TODO: If we could read more of the spec (ports, affinityType) of the
// existing load balancer, we could better determine if an update is
// necessary in more cases. For now, we optimistically assume that a
// matching IP suffices.
glog.Infof("LB already exists with endpoint %s for previously uncached service %s", endpoint, namespacedName)
if exists && api.LoadBalancerStatusEqual(status, &service.Status.LoadBalancer) {
glog.Infof("LB already exists with status %s for previously uncached service %s", status, namespacedName)
return nil, notRetryable
} else if exists {
glog.Infof("Deleting old LB for previously uncached service %s whose endpoint %s doesn't match the service's desired IPs %v",
namespacedName, endpoint, service.Spec.PublicIPs)
namespacedName, status, service.Spec.PublicIPs)
if err := s.balancer.EnsureTCPLoadBalancerDeleted(s.loadBalancerName(service), s.zone.Region); err != nil {
return err, retryable
}
@ -268,20 +264,24 @@ func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.Name
glog.V(2).Infof("Creating LB for service %s", namespacedName)
// The load balancer doesn't exist yet, so create it.
publicIPstring := fmt.Sprint(service.Spec.PublicIPs)
// Save the state so we can avoid a write if it doesn't change
previousState := api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer)
err := s.createExternalLoadBalancer(service)
if err != nil {
return fmt.Errorf("failed to create external load balancer for service %s: %v", namespacedName, err), retryable
}
if publicIPstring == fmt.Sprint(service.Spec.PublicIPs) {
// Write the state if changed
if api.LoadBalancerStatusEqual(previousState, &service.Status.LoadBalancer) {
glog.Infof("Not persisting unchanged service to registry.")
return nil, notRetryable
}
// If creating the load balancer succeeded, persist the updated service.
if err = s.persistUpdate(service); err != nil {
return fmt.Errorf("Failed to persist updated publicIPs 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
}
return nil, notRetryable
}
@ -301,13 +301,13 @@ func (s *ServiceController) persistUpdate(service *api.Service) error {
return nil
}
// TODO: Try to resolve the conflict if the change was unrelated to load
// balancers and public IPs. For now, just rely on the fact that we'll
// 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) {
glog.Infof("Not persisting update to service that has been changed since we received it: %v", err)
return nil
}
glog.Warningf("Failed to persist updated PublicIPs to service %s after creating its external load balancer: %v",
glog.Warningf("Failed to persist updated LoadBalancerStatus to service %s after creating its external load balancer: %v",
service.Name, err)
time.Sleep(clientRetryInterval)
}
@ -328,21 +328,23 @@ func (s *ServiceController) createExternalLoadBalancer(service *api.Service) err
for _, publicIP := range service.Spec.PublicIPs {
// TODO: Make this actually work for multiple IPs by using different
// names for each. For now, we'll just create the first and break.
endpoint, err := s.balancer.CreateTCPLoadBalancer(name, s.zone.Region, net.ParseIP(publicIP),
status, err := s.balancer.CreateTCPLoadBalancer(name, s.zone.Region, net.ParseIP(publicIP),
ports, hostsFromNodeList(nodes), service.Spec.SessionAffinity)
if err != nil {
return err
} else {
service.Status.LoadBalancer = *status
}
service.Spec.PublicIPs = []string{endpoint}
break
}
} else {
endpoint, err := s.balancer.CreateTCPLoadBalancer(name, s.zone.Region, nil,
status, err := s.balancer.CreateTCPLoadBalancer(name, s.zone.Region, nil,
ports, hostsFromNodeList(nodes), service.Spec.SessionAffinity)
if err != nil {
return err
} else {
service.Status.LoadBalancer = *status
}
service.Spec.PublicIPs = []string{endpoint}
}
return nil
}

View File

@ -72,9 +72,14 @@ func RunClusterInfo(factory *cmdutil.Factory, out io.Writer, cmd *cobra.Command)
services := r.Object.(*api.ServiceList).Items
for _, service := range services {
var link string
if len(service.Spec.PublicIPs) > 0 {
if len(service.Status.LoadBalancer.Ingress) > 0 {
ingress := service.Status.LoadBalancer.Ingress[0]
ip := ingress.IP
if ip == "" {
ip = ingress.Hostname
}
for _, port := range service.Spec.Ports {
link += "http://" + service.Spec.PublicIPs[0] + ":" + strconv.Itoa(port.Port) + " "
link += "http://" + ip + ":" + strconv.Itoa(port.Port) + " "
}
} else {
link = client.Host + "/api/v1beta3/proxy/namespaces/" + service.ObjectMeta.Namespace + "/services/" + service.ObjectMeta.Name

View File

@ -17,6 +17,7 @@ limitations under the License.
package kubectl
import (
"bytes"
"fmt"
"io"
"reflect"
@ -480,6 +481,22 @@ func (d *ServiceDescriber) Describe(namespace, name string) (string, error) {
return describeService(service, endpoints, events)
}
func buildIngressString(ingress []api.LoadBalancerIngress) string {
var buffer bytes.Buffer
for i := range ingress {
if i != 0 {
buffer.WriteString(", ")
}
if ingress[i].IP != "" {
buffer.WriteString(ingress[i].IP)
} else {
buffer.WriteString(ingress[i].Hostname)
}
}
return buffer.String()
}
func describeService(service *api.Service, endpoints *api.Endpoints, events *api.EventList) (string, error) {
if endpoints == nil {
endpoints = &api.Endpoints{}
@ -489,9 +506,9 @@ func describeService(service *api.Service, endpoints *api.Endpoints, events *api
fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(service.Labels))
fmt.Fprintf(out, "Selector:\t%s\n", formatLabels(service.Spec.Selector))
fmt.Fprintf(out, "IP:\t%s\n", service.Spec.PortalIP)
if len(service.Spec.PublicIPs) > 0 {
list := strings.Join(service.Spec.PublicIPs, ", ")
fmt.Fprintf(out, "Public IPs:\t%s\n", list)
if len(service.Status.LoadBalancer.Ingress) > 0 {
list := buildIngressString(service.Status.LoadBalancer.Ingress)
fmt.Fprintf(out, "Ingress:\t%s\n", list)
}
for i := range service.Spec.Ports {
sp := &service.Spec.Ports[i]

View File

@ -552,8 +552,12 @@ func printService(svc *api.Service, w io.Writer, withNamespace bool) error {
}
ips := []string{svc.Spec.PortalIP}
for _, publicIP := range svc.Spec.PublicIPs {
ips = append(ips, publicIP)
ingress := svc.Status.LoadBalancer.Ingress
for i := range ingress {
if ingress[i].IP != "" {
ips = append(ips, ingress[i].IP)
}
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d/%s\n", name, formatLabels(svc.Labels),

View File

@ -646,10 +646,6 @@ func TestPrintHumanReadableService(t *testing.T) {
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
PublicIPs: []string{
"2.3.4.5",
"3.4.5.6",
},
Ports: []api.ServicePort{
{
Port: 80,
@ -657,6 +653,18 @@ func TestPrintHumanReadableService(t *testing.T) {
},
},
},
Status: api.ServiceStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{
IP: "2.3.4.5",
},
{
IP: "3.4.5.6",
},
},
},
},
},
{
Spec: api.ServiceSpec{
@ -680,9 +688,6 @@ func TestPrintHumanReadableService(t *testing.T) {
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
PublicIPs: []string{
"2.3.4.5",
},
Ports: []api.ServicePort{
{
Port: 80,
@ -698,15 +703,19 @@ func TestPrintHumanReadableService(t *testing.T) {
},
},
},
Status: api.ServiceStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{
IP: "2.3.4.5",
},
},
},
},
},
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
PublicIPs: []string{
"2.3.4.5",
"4.5.6.7",
"5.6.7.8",
},
Ports: []api.ServicePort{
{
Port: 80,
@ -722,6 +731,22 @@ func TestPrintHumanReadableService(t *testing.T) {
},
},
},
Status: api.ServiceStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{
IP: "2.3.4.5",
},
{
IP: "3.4.5.6",
},
{
IP: "5.6.7.8",
Hostname: "host5678",
},
},
},
},
},
}
@ -734,9 +759,10 @@ func TestPrintHumanReadableService(t *testing.T) {
t.Errorf("expected to contain portal ip %s, but doesn't: %s", ip, output)
}
for _, ip = range svc.Spec.PublicIPs {
for _, ingress := range svc.Status.LoadBalancer.Ingress {
ip = ingress.IP
if !strings.Contains(output, ip) {
t.Errorf("expected to contain public ip %s, but doesn't: %s", ip, output)
t.Errorf("expected to contain ingress ip %s, but doesn't: %s", ip, output)
}
}
@ -748,8 +774,8 @@ func TestPrintHumanReadableService(t *testing.T) {
}
// Max of # ports and (# public ip + portal ip)
count := len(svc.Spec.Ports)
if len(svc.Spec.PublicIPs)+1 > count {
count = len(svc.Spec.PublicIPs) + 1
if len(svc.Status.LoadBalancer.Ingress)+1 > count {
count = len(svc.Status.LoadBalancer.Ingress) + 1
}
if count != strings.Count(output, "\n") {
t.Errorf("expected %d newlines, found %d", count, strings.Count(output, "\n"))

View File

@ -40,8 +40,8 @@ type serviceInfo struct {
proxyPort int
socket proxySocket
timeout time.Duration
publicIPs []string // TODO: make this net.IP
nodePort int
loadBalancerStatus api.LoadBalancerStatus
sessionAffinityType api.ServiceAffinity
stickyMaxAgeMinutes int
}
@ -287,9 +287,10 @@ func (proxier *Proxier) OnUpdate(services []api.Service) {
}
info.portalIP = serviceIP
info.portalPort = servicePort.Port
info.publicIPs = service.Spec.PublicIPs
// TODO(justinsb): switch to servicePort.NodePort when that lands
info.nodePort = 0
// Deep-copy in case the service instance changes
info.loadBalancerStatus = *api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer)
info.sessionAffinityType = service.Spec.SessionAffinity
glog.V(4).Infof("info: %+v", info)
@ -325,7 +326,7 @@ func sameConfig(info *serviceInfo, service *api.Service, port *api.ServicePort)
if !info.portalIP.Equal(net.ParseIP(service.Spec.PortalIP)) {
return false
}
if !ipsEqual(info.publicIPs, service.Spec.PublicIPs) {
if !api.LoadBalancerStatusEqual(&info.loadBalancerStatus, &service.Status.LoadBalancer) {
return false
}
if info.sessionAffinityType != service.Spec.SessionAffinity {
@ -351,10 +352,12 @@ func (proxier *Proxier) openPortal(service ServicePortName, info *serviceInfo) e
if err != nil {
return err
}
for _, publicIP := range info.publicIPs {
err = proxier.openOnePortal(net.ParseIP(publicIP), info.portalPort, info.protocol, proxier.listenIP, info.proxyPort, service)
if err != nil {
return err
for _, ingress := range info.loadBalancerStatus.Ingress {
if ingress.IP != "" {
err = proxier.openOnePortal(net.ParseIP(ingress.IP), info.portalPort, info.protocol, proxier.listenIP, info.proxyPort, service)
if err != nil {
return err
}
}
}
if info.nodePort != 0 {
@ -468,8 +471,10 @@ func (proxier *Proxier) openNodePort(nodePort int, protocol api.Protocol, proxyI
func (proxier *Proxier) closePortal(service ServicePortName, info *serviceInfo) error {
// Collect errors and report them all at the end.
el := proxier.closeOnePortal(info.portalIP, info.portalPort, info.protocol, proxier.listenIP, info.proxyPort, service)
for _, publicIP := range info.publicIPs {
el = append(el, proxier.closeOnePortal(net.ParseIP(publicIP), info.portalPort, info.protocol, proxier.listenIP, info.proxyPort, service)...)
for _, ingress := range info.loadBalancerStatus.Ingress {
if ingress.IP != "" {
el = append(el, proxier.closeOnePortal(net.ParseIP(ingress.IP), info.portalPort, info.protocol, proxier.listenIP, info.proxyPort, service)...)
}
}
if info.nodePort != 0 {
el = append(el, proxier.closeNodePort(info.nodePort, info.protocol, proxier.listenIP, info.proxyPort, service)...)

View File

@ -289,10 +289,14 @@ var _ = Describe("Services", func() {
// currently indicated by a public IP address being added to the spec.
result, err = waitForPublicIPs(c, serviceName, ns)
Expect(err).NotTo(HaveOccurred())
if len(result.Spec.PublicIPs) != 1 {
Failf("got unexpected number (%d) of public IPs for externally load balanced service: %v", result.Spec.PublicIPs, result)
if len(result.Status.LoadBalancer.Ingress) != 1 {
Failf("got unexpected number (%v) of ingress points for externally load balanced service: %v", result.Status.LoadBalancer.Ingress, result)
}
ingress := result.Status.LoadBalancer.Ingress[0]
ip := ingress.IP
if ip == "" {
ip = ingress.Hostname
}
ip := result.Spec.PublicIPs[0]
port := result.Spec.Ports[0].Port
pod := &api.Pod{
@ -370,7 +374,7 @@ var _ = Describe("Services", func() {
},
}
publicIPs := []string{}
ingressPoints := []string{}
for _, namespace := range namespaces {
for _, serviceName := range serviceNames {
service.ObjectMeta.Name = serviceName
@ -389,10 +393,16 @@ var _ = Describe("Services", func() {
for _, serviceName := range serviceNames {
result, err := waitForPublicIPs(c, serviceName, namespace)
Expect(err).NotTo(HaveOccurred())
publicIPs = append(publicIPs, result.Spec.PublicIPs...) // Save 'em to check uniqueness
for i := range result.Status.LoadBalancer.Ingress {
ingress := result.Status.LoadBalancer.Ingress[i].IP
if ingress == "" {
ingress = result.Status.LoadBalancer.Ingress[i].Hostname
}
ingressPoints = append(ingressPoints, ingress) // Save 'em to check uniqueness
}
}
}
validateUniqueOrFail(publicIPs)
validateUniqueOrFail(ingressPoints)
})
})
@ -406,12 +416,12 @@ func waitForPublicIPs(c *client.Client, serviceName, namespace string) (*api.Ser
Logf("Get service failed, ignoring for 5s: %v", err)
continue
}
if len(service.Spec.PublicIPs) > 0 {
if len(service.Status.LoadBalancer.Ingress) > 0 {
return service, nil
}
Logf("Waiting for service %s in namespace %s to have a public IP (%v)", serviceName, namespace, time.Since(start))
Logf("Waiting for service %s in namespace %s to have an ingress point (%v)", serviceName, namespace, time.Since(start))
}
return service, fmt.Errorf("service %s in namespace %s doesn't have a public IP after %.2f seconds", serviceName, namespace, timeout.Seconds())
return service, fmt.Errorf("service %s in namespace %s doesn't have an ingress point after %.2f seconds", serviceName, namespace, timeout.Seconds())
}
func validateUniqueOrFail(s []string) {