Skip to content

Commit

Permalink
Ingress will only have an IP address when type is LoadBalancer (#5047)
Browse files Browse the repository at this point in the history
  • Loading branch information
divolgin authored Dec 17, 2024
1 parent 3cd76c8 commit 02e3180
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 27 deletions.
43 changes: 29 additions & 14 deletions pkg/appstate/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/replicatedhq/kots/pkg/appstate/types"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -118,6 +119,7 @@ func makeIngressResourceState(r *networkingv1.Ingress, state types.State) types.
}

func CalculateIngressState(clientset kubernetes.Interface, r *networkingv1.Ingress) types.State {
ctx := context.TODO()
ns := r.Namespace
backend := r.Spec.DefaultBackend

Expand All @@ -139,30 +141,43 @@ func CalculateIngressState(clientset kubernetes.Interface, r *networkingv1.Ingre
ns = metav1.NamespaceSystem
}

var states []types.State
services := []*v1.Service{} // includes nils which are mapped to unavailable
if backend != nil {
states = append(states, ingressGetStateFromBackend(clientset, ns, *backend))
service, _ := clientset.CoreV1().Services(ns).Get(ctx, backend.Service.Name, metav1.GetOptions{})
services = append(services, service)
}

for _, rules := range r.Spec.Rules {
for _, path := range rules.HTTP.Paths {
states = append(states, ingressGetStateFromBackend(clientset, r.Namespace, path.Backend))
service, _ := clientset.CoreV1().Services(r.Namespace).Get(ctx, path.Backend.Service.Name, metav1.GetOptions{})
services = append(services, service)
}
}
// https://github.com/kubernetes/kubernetes/blob/badcd4af3f592376ce891b7c1b7a43ed6a18a348/pkg/printers/internalversion/printers.go#L1067
states = append(states, ingressGetStateFromExternalIP(r))
return types.MinState(states...)
}

func ingressGetStateFromBackend(clientset kubernetes.Interface, namespace string, backend networkingv1.IngressBackend) (minState types.State) {
if backend.Service == nil {
return types.StateUnavailable
hasLoadBalancer := false
for _, service := range services {
if service != nil && service.Spec.Type == v1.ServiceTypeLoadBalancer {
hasLoadBalancer = true
break
}
}
service, _ := clientset.CoreV1().Services(namespace).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{})
if service == nil {
return types.StateUnavailable

var states []types.State
for _, service := range services {
if service == nil {
states = append(states, types.StateUnavailable)
} else {
states = append(states, serviceGetStateFromEndpoints(clientset, service))
}
}

// An ingress will have an IP associated with it if it's type is LoadBalancer.
if hasLoadBalancer {
// https://github.com/kubernetes/kubernetes/blob/badcd4af3f592376ce891b7c1b7a43ed6a18a348/pkg/printers/internalversion/printers.go#L1067
states = append(states, ingressGetStateFromExternalIP(r))
}
return serviceGetStateFromEndpoints(clientset, service)

return types.MinState(states...)
}

func ingressGetStateFromExternalIP(ing *networkingv1.Ingress) types.State {
Expand Down
165 changes: 152 additions & 13 deletions pkg/appstate/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

func mockClientsetK8sVersion(expectedMajor string, expectedMinor string) kubernetes.Interface {
clientset := fake.NewSimpleClientset(
// add a service
// Defaul backend service and endpoint
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "default-http-backend",
Expand Down Expand Up @@ -52,6 +52,82 @@ func mockClientsetK8sVersion(expectedMajor string, expectedMinor string) kuberne
},
},
},

// LoadBalancer service and endpoint
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "app-lb",
Namespace: "",
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
{
Name: "http",
Port: 8080,
},
},
},
},
&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "app-lb",
Namespace: "",
},
Subsets: []v1.EndpointSubset{
{
Ports: []v1.EndpointPort{
{
Name: "http",
Port: 8080,
},
},
Addresses: []v1.EndpointAddress{
{
IP: "172.0.0.2",
},
},
},
},
},

// NodePort service and endpoint
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "app-nodeport",
Namespace: "",
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeNodePort,
Ports: []v1.ServicePort{
{
Name: "http",
Port: 8080,
},
},
},
},
&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "app-nodeport",
Namespace: "",
},
Subsets: []v1.EndpointSubset{
{
Ports: []v1.EndpointPort{
{
Name: "http",
Port: 8080,
},
},
Addresses: []v1.EndpointAddress{
{
IP: "172.0.0.2",
},
},
},
},
},
)
clientset.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = &version.Info{
Major: expectedMajor,
Expand Down Expand Up @@ -88,22 +164,30 @@ func TestCalculateIngressState(t *testing.T) {
},
},
want: types.StateReady,
},
{
name: "expect unavailable state when ingress with k8s version > 1.22 and no default backend",
args: args{
clientset: mockClientsetK8sVersion("1", "23"),
r: &networkingv1.Ingress{
Spec: networkingv1.IngressSpec{},
},
},
want: types.StateUnavailable,
}, {
name: "expect ready state when ingress with k8s version > 1.22 and no default backend and with load balancer status",
name: "expect ready state when there is a load balancer and an IP address",
args: args{
clientset: mockClientsetK8sVersion("1", "23"),
r: &networkingv1.Ingress{
Spec: networkingv1.IngressSpec{},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "app-lb",
},
},
},
},
},
},
},
},
},
Status: networkingv1.IngressStatus{
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
Ingress: []networkingv1.IngressLoadBalancerIngress{
Expand All @@ -116,6 +200,61 @@ func TestCalculateIngressState(t *testing.T) {
},
},
want: types.StateReady,
}, {
name: "expect ready state when there is no LoadBalancer and no address is assigned",
args: args{
clientset: mockClientsetK8sVersion("1", "23"),
r: &networkingv1.Ingress{
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "app-nodeport",
},
},
},
},
},
},
},
},
},
Status: networkingv1.IngressStatus{},
},
},
want: types.StateReady,
}, {
name: "expect unavailable state when there is a LoadBalancer but no address is assigned",
args: args{
clientset: mockClientsetK8sVersion("1", "23"),
r: &networkingv1.Ingress{
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "app-lb",
},
},
},
},
},
},
},
},
},
},
},
want: types.StateUnavailable,
},
}
for _, tt := range tests {
Expand Down

0 comments on commit 02e3180

Please sign in to comment.