From 3884d5fc59943e5745e3b7772898d0c160a3f754 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 22 May 2015 17:33:29 -0400 Subject: [PATCH] Add LoadBalancer status to ServiceStatus This will replace publicIPs --- pkg/api/helpers.go | 37 +++++++++++ pkg/api/types.go | 25 +++++++- pkg/api/v1/conversion_generated.go | 62 +++++++++++++++++++ pkg/api/v1/types.go | 25 +++++++- pkg/api/v1beta1/conversion.go | 20 ++++++ pkg/api/v1beta1/types.go | 23 +++++++ pkg/api/v1beta2/conversion.go | 20 ++++++ pkg/api/v1beta2/types.go | 23 +++++++ pkg/api/v1beta3/conversion_generated.go | 62 +++++++++++++++++++ pkg/api/v1beta3/types.go | 25 +++++++- pkg/cloudprovider/cloud.go | 8 +-- pkg/cloudprovider/fake/fake.go | 15 +++-- pkg/cloudprovider/gce/gce.go | 30 +++++---- pkg/cloudprovider/openstack/openstack.go | 35 ++++++----- .../servicecontroller/servicecontroller.go | 36 ++++++----- pkg/kubectl/cmd/clusterinfo.go | 9 ++- pkg/kubectl/describe.go | 23 ++++++- pkg/kubectl/resource_printer.go | 8 ++- pkg/kubectl/resource_printer_test.go | 58 ++++++++++++----- pkg/proxy/proxier.go | 23 ++++--- test/e2e/service.go | 28 ++++++--- 21 files changed, 500 insertions(+), 95 deletions(-) diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 0c589518eb5..c85ac6b400c 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -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 +} diff --git a/pkg/api/types.go b/pkg/api/types.go index c4a516e8210..e610927e6cb 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -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 { diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index 21d2b1de640..10615e269ce 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -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, diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 057569ad4db..8147eb5d69a 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -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 { diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 28af6f18188..15979c24827 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -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 }, diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 2f82854ad82..ad0127ab684 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -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 { diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index c17bb1feb65..de99dd8aacc 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -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 }, diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index e27ac6a6244..2031451dbdf 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -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 { diff --git a/pkg/api/v1beta3/conversion_generated.go b/pkg/api/v1beta3/conversion_generated.go index 19ff15bbe1b..593ce270213 100644 --- a/pkg/api/v1beta3/conversion_generated.go +++ b/pkg/api/v1beta3/conversion_generated.go @@ -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, diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index edaeaa69a60..f8d4a574ead 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -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 { diff --git a/pkg/cloudprovider/cloud.go b/pkg/cloudprovider/cloud.go index e60d885f60e..31054f1e3bd 100644 --- a/pkg/cloudprovider/cloud.go +++ b/pkg/cloudprovider/cloud.go @@ -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 diff --git a/pkg/cloudprovider/fake/fake.go b/pkg/cloudprovider/fake/fake.go index 76711ac5fb2..d9db3b6c187 100644 --- a/pkg/cloudprovider/fake/fake.go +++ b/pkg/cloudprovider/fake/fake.go @@ -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. diff --git a/pkg/cloudprovider/gce/gce.go b/pkg/cloudprovider/gce/gce.go index 9284eb75ee5..b7451b97bb3 100644 --- a/pkg/cloudprovider/gce/gce.go +++ b/pkg/cloudprovider/gce/gce.go @@ -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. diff --git a/pkg/cloudprovider/openstack/openstack.go b/pkg/cloudprovider/openstack/openstack.go index e30f7a07cf2..fb855bec152 100644 --- a/pkg/cloudprovider/openstack/openstack.go +++ b/pkg/cloudprovider/openstack/openstack.go @@ -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 { diff --git a/pkg/cloudprovider/servicecontroller/servicecontroller.go b/pkg/cloudprovider/servicecontroller/servicecontroller.go index 6833b9aa7b7..de5664cd6c2 100644 --- a/pkg/cloudprovider/servicecontroller/servicecontroller.go +++ b/pkg/cloudprovider/servicecontroller/servicecontroller.go @@ -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 } diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index 4b419e435f7..28efaff3905 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -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 diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 48083c25943..4805f79fbe2 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -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] diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index b2acf1ff8ec..30da9dab6a1 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -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), diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index 7f520576cc8..fe0d2662510 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -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")) diff --git a/pkg/proxy/proxier.go b/pkg/proxy/proxier.go index 31e568b1ff9..eeef23aeb4d 100644 --- a/pkg/proxy/proxier.go +++ b/pkg/proxy/proxier.go @@ -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)...) diff --git a/test/e2e/service.go b/test/e2e/service.go index 07f84da7a85..6f50b152da1 100644 --- a/test/e2e/service.go +++ b/test/e2e/service.go @@ -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) {