Skip to content

fix: Fix generation of externalIPs facet #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func newK8sEventsProcessor(logger *zap.Logger, cfg component.Config) *K8sEventsP
NewEndpointsStatusAction(),
},
serviceActions: []serviceAction{
NewServiceLBIngressAction(), NewServiceSelectorAction(), NewServicePortsAction(),
NewServiceLBIngressAction(), NewServiceSelectorAction(), NewServicePortsAction(), NewServiceExternalIPsAction(),
},
serviceAccountActions: []serviceAccountAction{
NewServiceAccountSecretsNamesAction(), NewServiceAccountSecretsAction(), NewServiceAccountImagePullSecretsAction(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,13 @@ import (
)

const (
ServiceLBIngressAttributeKey = "loadBalancerIngress"
ServicePortsAttributeKey = "ports"
ServiceLBIngressAttributeKey = "loadBalancerIngress"
ServiceExternalIPsAttributeKey = "externalIPs"
ServicePortsAttributeKey = "ports"
)

// ---------------------------------- Service "loadBalancerIngress" ----------------------------------

type ServiceLBIngressAction struct{}

func NewServiceLBIngressAction() ServiceLBIngressAction {
return ServiceLBIngressAction{}
}

// loadBalancerStatusStringer behaves mostly like a string interface and converts the given status to a string.
// Adapted from https://github.com/kubernetes/kubernetes/blob/0d3b859af81e6a5f869a7766c8d45afd1c600b04/pkg/printers/internalversion/printers.go#L1275
func loadBalancerStatusStringer(s corev1.LoadBalancerStatus) string {
func loadBalancerStatusStringer(s corev1.LoadBalancerStatus) []string {
ingress := s.Ingress
result := sets.NewString()
for i := range ingress {
Expand All @@ -34,25 +26,64 @@ func loadBalancerStatusStringer(s corev1.LoadBalancerStatus) string {
}
}

r := strings.Join(result.List(), ",")
return r
return result.List()
}

// Adapted from https://github.com/kubernetes/kubernetes/blob/0d3b859af81e6a5f869a7766c8d45afd1c600b04/pkg/printers/internalversion/printers.go#L1293
func getServiceExternalIP(svc *corev1.Service) string {
lbIps := loadBalancerStatusStringer(svc.Status.LoadBalancer)
if len(svc.Spec.ExternalIPs) > 0 {
results := []string{}
if len(lbIps) > 0 {
results = append(results, strings.Split(lbIps, ",")...)
// Generates the service's externalIPs, based on the service type
// Returns an array of externalIPs and TRUE if there is any external IP.
// Returns an array with a single string "None", "Pending" or "Unknown" and FALSE if there are no external IPs.
func getServiceExternalIPs(svc corev1.Service) ([]string, bool) {
switch svc.Spec.Type {
case corev1.ServiceTypeClusterIP:
if len(svc.Spec.ExternalIPs) > 0 {
return svc.Spec.ExternalIPs, true
}
return []string{"None"}, false
case corev1.ServiceTypeNodePort:
if len(svc.Spec.ExternalIPs) > 0 {
return svc.Spec.ExternalIPs, true
}
return []string{"None"}, false
case corev1.ServiceTypeLoadBalancer:
results := loadBalancerStatusStringer(svc.Status.LoadBalancer)
results = append(results, svc.Spec.ExternalIPs...)
return strings.Join(results, ",")
if len(results) > 0 {
return results, true
}
return []string{"Pending"}, false
case corev1.ServiceTypeExternalName:
return []string{svc.Spec.ExternalName}, true
}
if len(lbIps) > 0 {
return lbIps
return []string{"Unknown"}, false
}

type ServiceExternalIPsAction struct{}

func NewServiceExternalIPsAction() ServiceExternalIPsAction {
return ServiceExternalIPsAction{}
}

// Generates the Service "loadBalancerIngress" facet.
// We return an array of IPs (even if there's only a single IP) if there is at least one.
// We set the facet to a string (no array in this case) as follows:
//
// - "None" --> service != LoadBalancer and there are no external IPs
// - "Pending" --> service is a LoadBalancer and there are no external IPs
// - "Unknown" --> service is of an unknown type
func (ServiceExternalIPsAction) ComputeAttributes(service corev1.Service) (attributes, error) {
if externalIPs, ok := getServiceExternalIPs(service); ok {
return attributes{ServiceExternalIPsAttributeKey: externalIPs}, nil
} else {
return attributes{ServiceExternalIPsAttributeKey: externalIPs[0]}, nil
}
return "<pending>"
}

// ---------------------------------- Service "loadBalancerIngress" ----------------------------------

type ServiceLBIngressAction struct{}

func NewServiceLBIngressAction() ServiceLBIngressAction {
return ServiceLBIngressAction{}
}

// Generates the Service "loadBalancerIngress" facet.
Expand All @@ -61,7 +92,8 @@ func (ServiceLBIngressAction) ComputeAttributes(service corev1.Service) (attribu
return attributes{}, nil
}

return attributes{ServiceLBIngressAttributeKey: getServiceExternalIP(&service)}, nil
ingress := loadBalancerStatusStringer(service.Status.LoadBalancer)
return attributes{ServiceLBIngressAttributeKey: strings.Join(ingress, ",")}, nil

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,45 @@ func TestServiceActions(t *testing.T) {
},
},
{
name: "Service LB Ingress of non-LB service",
name: "LB Ingress (working Service)",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceLoadBalancer.json"),
expectedResults: []queryWithResult{
{"observe_transform.facets.loadBalancerIngress", "someLoadBalancerIngressIdentifier.elb.us-west-2.amazonaws.com"},
},
},
{
name: "Service ports",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceClusterIPEvent.json"),
expectedResults: []queryWithResult{
{"observe_transform.facets.loadBalancerIngress", nil},
{"observe_transform.facets.ports", "6379/TCP"},
},
},
{
name: "External IPs",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceLoadBalancer.json"),
expectedResults: []queryWithResult{
{"observe_transform.facets.externalIPs", []interface{}{"someLoadBalancerIngressIdentifier.elb.us-west-2.amazonaws.com"}},
},
},
{
name: "Service LB Ingress of initializing LB service",
name: "Pending LB",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceLoadBalancerPendingEvent.json"),
expectedResults: []queryWithResult{
{"observe_transform.facets.loadBalancerIngress", "<pending>"},
{"observe_transform.facets.externalIPs", "Pending"},
},
},
{
name: "Service LB Ingress of working LB service",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceLoadBalancerIngressEvent.json"),
name: "Unknown Service type",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceUnknown.json"),
expectedResults: []queryWithResult{
{"observe_transform.facets.loadBalancerIngress", "someLoadBalancerIdentifier.elb.us-west-2.amazonaws.com"},
{"observe_transform.facets.externalIPs", "Unknown"},
},
},
{
name: "Service ports",
name: "No External IPs",
inLogs: resourceLogsFromSingleJsonEvent("./testdata/serviceClusterIPEvent.json"),
expectedResults: []queryWithResult{
{"observe_transform.facets.ports", "6379/TCP"},
{"observe_transform.facets.externalIPs", "None"},
},
},
} {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"frontend-external\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"name\":\"http\",\"port\":80,\"targetPort\":8080}],\"selector\":{\"app\":\"frontend\"},\"type\":\"LoadBalancer\"}}\n"
},
"creationTimestamp": "2024-07-10T05:34:45Z",
"finalizers": ["service.kubernetes.io/load-balancer-cleanup"],
"managedFields": [
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:kubectl.kubernetes.io/last-applied-configuration": {}
}
},
"f:spec": {
"f:allocateLoadBalancerNodePorts": {},
"f:externalTrafficPolicy": {},
"f:internalTrafficPolicy": {},
"f:ports": {
".": {},
"k:{\"port\":80,\"protocol\":\"TCP\"}": {
".": {},
"f:name": {},
"f:port": {},
"f:protocol": {},
"f:targetPort": {}
}
},
"f:selector": {},
"f:sessionAffinity": {},
"f:type": {}
}
},
"manager": "kubectl-client-side-apply",
"operation": "Update",
"time": "2024-07-10T05:34:45Z"
},
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:finalizers": {
".": {},
"v:\"service.kubernetes.io/load-balancer-cleanup\"": {}
}
},
"f:status": { "f:loadBalancer": { "f:ingress": {} } }
},
"manager": "aws-cloud-controller-manager",
"operation": "Update",
"subresource": "status",
"time": "2024-07-10T05:34:48Z"
}
],
"name": "frontend-external",
"namespace": "default",
"resourceVersion": "16090058",
"uid": "8d10f98f-23ed-47cc-9617-9261b841f6cc"
},
"spec": {
"allocateLoadBalancerNodePorts": true,
"clusterIP": "10.100.111.179",
"clusterIPs": ["10.100.111.179"],
"externalTrafficPolicy": "Cluster",
"internalTrafficPolicy": "Cluster",
"ipFamilies": ["IPv4"],
"ipFamilyPolicy": "SingleStack",
"ports": [
{
"name": "http",
"nodePort": 32700,
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": { "app": "frontend" },
"sessionAffinity": "None",
"type": "LoadBalancer"
},
"status": {
"loadBalancer": {
"ingress": [
{
"hostname": "someLoadBalancerIngressIdentifier.elb.us-west-2.amazonaws.com"
}
]
}
}
}
Loading
Loading