Skip to content
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

Add Health to Logstash Status #7528

Merged
merged 2 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions config/crds/v1/all-crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8909,6 +8909,10 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Health
jsonPath: .status.health
name: health
type: string
- description: Available nodes
jsonPath: .status.availableNodes
name: available
Expand Down Expand Up @@ -10061,6 +10065,8 @@ spec:
expectedNodes:
format: int32
type: integer
health:
type: string
monitoringAssociationStatus:
additionalProperties:
description: AssociationStatus is the status of an association resource.
Expand Down
6 changes: 6 additions & 0 deletions config/crds/v1/bases/logstash.k8s.elastic.co_logstashes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Health
jsonPath: .status.health
name: health
type: string
- description: Available nodes
jsonPath: .status.availableNodes
name: available
Expand Down Expand Up @@ -8680,6 +8684,8 @@ spec:
expectedNodes:
format: int32
type: integer
health:
type: string
monitoringAssociationStatus:
additionalProperties:
description: AssociationStatus is the status of an association resource.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8963,6 +8963,10 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Health
jsonPath: .status.health
name: health
type: string
- description: Available nodes
jsonPath: .status.availableNodes
name: available
Expand Down Expand Up @@ -10115,6 +10119,8 @@ spec:
expectedNodes:
format: int32
type: integer
health:
type: string
monitoringAssociationStatus:
additionalProperties:
description: AssociationStatus is the status of an association resource.
Expand Down
19 changes: 19 additions & 0 deletions pkg/apis/logstash/v1alpha1/logstash_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,26 @@ import (
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/hash"
)

type LogstashHealth string

const (
LogstashContainerName = "logstash"
// Kind is inferred from the struct name using reflection in SchemeBuilder.Register()
// we duplicate it as a constant here for practical purposes.
Kind = "Logstash"

// LogstashRedHealth means that the health is neither yellow nor green.
LogstashRedHealth LogstashHealth = "red"

// LogstashYellowHealth means that:
// 1) at least one Pod is Ready, and
// 2) any associations are configured and established
LogstashYellowHealth LogstashHealth = "yellow"

// LogstashGreenHealth means that:
// 1) all Pods are Ready, and
// 2) any associations are configured and established
LogstashGreenHealth LogstashHealth = "green"
)

// LogstashSpec defines the desired state of Logstash
Expand Down Expand Up @@ -129,6 +144,9 @@ type LogstashStatus struct {
// +kubebuilder:validation:Optional
AvailableNodes int32 `json:"availableNodes,omitempty"`

// +kubebuilder:validation:Optional
Health LogstashHealth `json:"health,omitempty"`

// ObservedGeneration is the most recent generation observed for this Logstash instance.
// It corresponds to the metadata generation, which is updated on mutation by the API Server.
// If the generation observed in status diverges from the generation in metadata, the Logstash
Expand All @@ -150,6 +168,7 @@ type LogstashStatus struct {
// +k8s:openapi-gen=true
// +kubebuilder:resource:categories=elastic,shortName=ls
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="health",type="string",JSONPath=".status.health",description="Health"
// +kubebuilder:printcolumn:name="available",type="integer",JSONPath=".status.availableNodes",description="Available nodes"
// +kubebuilder:printcolumn:name="expected",type="integer",JSONPath=".status.expectedNodes",description="Expected nodes"
// +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp"
Expand Down
37 changes: 37 additions & 0 deletions pkg/controller/logstash/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package logstash

import (
v1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
lsv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/logstash/v1alpha1"
)

// CalculateHealth returns health of Logstash based on association status, desired count and ready count.
func CalculateHealth(associations []v1.Association, ready, desired int32) (lsv1alpha1.LogstashHealth, error) {
for _, assoc := range associations {
assocConf, err := assoc.AssociationConf()
if err != nil {
return "", err
}
if assocConf.IsConfigured() {
statusMap := assoc.AssociationStatusMap(assoc.AssociationType())
if !statusMap.AllEstablished() || len(statusMap) == 0 {
return lsv1alpha1.LogstashRedHealth, nil
}
}
}

switch {
case ready == 0:
return lsv1alpha1.LogstashRedHealth, nil
case ready == desired:
return lsv1alpha1.LogstashGreenHealth, nil
case ready > 0:
return lsv1alpha1.LogstashYellowHealth, nil
default:
return lsv1alpha1.LogstashRedHealth, nil
}
}
130 changes: 130 additions & 0 deletions pkg/controller/logstash/health_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package logstash

import (
"testing"

"github.com/stretchr/testify/require"

commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
lsv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/logstash/v1alpha1"
)

func Test_calculateHealth(t *testing.T) {
type params struct {
esAssoc bool
esAssocEstablished bool
}

var noAssociation []commonv1.Association
createAssociation := func(assocDef params) []commonv1.Association {
ls := &lsv1alpha1.Logstash{
Spec: lsv1alpha1.LogstashSpec{},
}
var result []commonv1.Association
dummyConf := commonv1.AssociationConf{
AuthSecretName: "name",
AuthSecretKey: "key",
CACertProvided: true,
CASecretName: "name",
URL: "url",
}
cluster := lsv1alpha1.ElasticsearchCluster{
ObjectSelector: commonv1.ObjectSelector{
Name: "es",
Namespace: "a",
},
ClusterName: "test",
}
if assocDef.esAssoc {
ls.Spec.ElasticsearchRefs = []lsv1alpha1.ElasticsearchCluster{cluster}
esAssoc := lsv1alpha1.LogstashESAssociation{Logstash: ls}
esAssoc.SetAssociationConf(&dummyConf)
if assocDef.esAssocEstablished {
_ = esAssoc.SetAssociationStatusMap(
commonv1.ElasticsearchAssociationType,
map[string]commonv1.AssociationStatus{"a/es": commonv1.AssociationEstablished})
}
result = append(result, &esAssoc)
}
return result
}

for _, tt := range []struct {
name string
associations []commonv1.Association
ready, desired int32
want lsv1alpha1.LogstashHealth
}{
{
name: "no association, 0 desired",
associations: noAssociation,
ready: 0,
desired: 0,
want: lsv1alpha1.LogstashRedHealth,
},
{
name: "no association, all ready",
associations: noAssociation,
ready: 3,
desired: 3,
want: lsv1alpha1.LogstashGreenHealth,
},
{
name: "no association, some ready",
associations: noAssociation,
ready: 1,
desired: 5,
want: lsv1alpha1.LogstashYellowHealth,
},
{
name: "no association, none ready",
associations: noAssociation,
ready: 0,
desired: 4,
want: lsv1alpha1.LogstashRedHealth,
},
{
name: "association not established, all ready",
associations: createAssociation(params{esAssoc: true, esAssocEstablished: false}),
ready: 2,
desired: 2,
want: lsv1alpha1.LogstashRedHealth,
},
{
name: "association established, 0 desired",
associations: createAssociation(params{esAssoc: true, esAssocEstablished: true}),
want: lsv1alpha1.LogstashRedHealth,
},
{
name: "association established, all ready",
associations: createAssociation(params{esAssoc: true, esAssocEstablished: true}),
ready: 2,
desired: 2,
want: lsv1alpha1.LogstashGreenHealth,
},
{
name: "association established, some ready",
associations: createAssociation(params{esAssoc: true, esAssocEstablished: true}),
ready: 1,
desired: 5,
want: lsv1alpha1.LogstashYellowHealth,
},
{
name: "association established, none ready",
associations: createAssociation(params{esAssoc: true, esAssocEstablished: true}),
ready: 0,
desired: 4,
want: lsv1alpha1.LogstashRedHealth,
},
} {
t.Run(tt.name, func(t *testing.T) {
got, err := CalculateHealth(tt.associations, tt.ready, tt.desired)
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}
4 changes: 4 additions & 0 deletions pkg/controller/logstash/logstash_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ func TestReconcileLogstash_Reconcile(t *testing.T) {
Version: "8.12.0",
ExpectedNodes: 1,
AvailableNodes: 1,
Health: logstashv1alpha1.LogstashGreenHealth,
ObservedGeneration: 2,
Selector: "common.k8s.elastic.co/type=logstash,logstash.k8s.elastic.co/name=testLogstash",
},
Expand Down Expand Up @@ -314,6 +315,7 @@ func TestReconcileLogstash_Reconcile(t *testing.T) {
Version: "8.12.0",
ExpectedNodes: 1,
AvailableNodes: 1,
Health: logstashv1alpha1.LogstashGreenHealth,
ObservedGeneration: 2,
Selector: "common.k8s.elastic.co/type=logstash,logstash.k8s.elastic.co/name=testLogstash",
},
Expand Down Expand Up @@ -413,6 +415,7 @@ func TestReconcileLogstash_Reconcile(t *testing.T) {
Version: "8.12.0",
ExpectedNodes: 1,
AvailableNodes: 1,
Health: logstashv1alpha1.LogstashGreenHealth,
ObservedGeneration: 2,
Selector: "common.k8s.elastic.co/type=logstash,logstash.k8s.elastic.co/name=testLogstash",
},
Expand Down Expand Up @@ -503,6 +506,7 @@ func TestReconcileLogstash_Reconcile(t *testing.T) {
Status: logstashv1alpha1.LogstashStatus{
ExpectedNodes: 1,
AvailableNodes: 1,
Health: logstashv1alpha1.LogstashGreenHealth,
ObservedGeneration: 2,
Selector: "common.k8s.elastic.co/type=logstash,logstash.k8s.elastic.co/name=testLogstash",
},
Expand Down
7 changes: 7 additions & 0 deletions pkg/controller/logstash/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ func calculateStatus(params *Params, sset appsv1.StatefulSet) (logstashv1alpha1.
status.Version = common.LowestVersionFromPods(params.Context, status.Version, pods, VersionLabelName)
status.AvailableNodes = sset.Status.ReadyReplicas
status.ExpectedNodes = sset.Status.Replicas

health, err := CalculateHealth(logstash.GetAssociations(), status.AvailableNodes, status.ExpectedNodes)
if err != nil {
return status, err
}
status.Health = health

return status, nil
}

Expand Down