Skip to content

Commit

Permalink
Add reporters for Sts/Deployment status
Browse files Browse the repository at this point in the history
Signed-off-by: Saswata Mukherjee <saswataminsta@yahoo.com>
  • Loading branch information
saswatamcode committed Sep 8, 2024
1 parent d4c5386 commit b07cdec
Show file tree
Hide file tree
Showing 4 changed files with 397 additions and 0 deletions.
180 changes: 180 additions & 0 deletions internal/pkg/status/deployment_reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// This file is heavily inspired by Prometheus Operator.
//
// Copyright 2022 The prometheus-operator Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package status

import (
"context"
"fmt"
"strings"
"time"

appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type DeploymentReporter struct {
Pods []*Pod
deployment *appsv1.Deployment
replicaSets *appsv1.ReplicaSetList
}

// NewDeploymentReporter returns a deployment's reporter.
func NewDeploymentReporter(ctx context.Context, c client.Client, deployment *appsv1.Deployment) (*DeploymentReporter, error) {
if deployment == nil {
// deployment is nil when the controller couldn't create the deployment
// (incompatible spec fields for instance).
return &DeploymentReporter{
Pods: []*Pod{},
}, nil
}

ls, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
if err != nil {
// Something is really broken if the deployment's selector isn't valid.
panic(err)
}

pods := &v1.PodList{}
if err := c.List(ctx, pods, client.InNamespace(deployment.Namespace), client.MatchingLabelsSelector{Selector: ls}); err != nil {
return nil, err
}

replicaSets := &appsv1.ReplicaSetList{}
if err := c.List(ctx, replicaSets, client.InNamespace(deployment.Namespace), client.MatchingLabelsSelector{Selector: ls}); err != nil {
return nil, err
}

deploymentReporter := &DeploymentReporter{
deployment: deployment,
replicaSets: replicaSets,
Pods: make([]*Pod, 0, len(pods.Items)),
}
for _, p := range pods.Items {
var found bool
for _, owner := range p.ObjectMeta.OwnerReferences {
if owner.Kind == "Deployment" && owner.Name == deployment.Name {
found = true
break
}
}

if !found {
continue
}

deploymentReporter.Pods = append(deploymentReporter.Pods, ptr.To(Pod(p)))
}

return deploymentReporter, nil
}

// UpdatedPods returns the list of pods that match with the deployment's revision.
func (dr *DeploymentReporter) UpdatedPods() []*Pod {
return dr.filterPods(func(p *Pod) bool {
return dr.IsUpdated(p)
})
}

// IsUpdated returns true if the given pod matches with the deployment's revision.
func (dr *DeploymentReporter) IsUpdated(p *Pod) bool {
for _, rs := range dr.replicaSets.Items {
if rs.Labels["pod-template-hash"] == p.Labels["pod-template-hash"] {
if dr.deployment.Annotations["deployment.kubernetes.io/revision"] == rs.Annotations["deployment.kubernetes.io/revision"] {
return true
}
}
}
return false
}

// ReadyPods returns the list of pods that are ready.
func (dr *DeploymentReporter) ReadyPods() []*Pod {
return dr.filterPods(func(p *Pod) bool {
return p.Ready()
})
}

func (dr *DeploymentReporter) filterPods(f func(*Pod) bool) []*Pod {
pods := make([]*Pod, 0, len(dr.Pods))

for _, p := range dr.Pods {
if f(p) {
pods = append(pods, p)
}
}

return pods
}

func (dr *DeploymentReporter) Update(gObj GoverningObject) metav1.Condition {
fmt.Println("Updating status")

Check failure on line 127 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
fmt.Println("Deployment", dr.deployment)

Check failure on line 128 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
fmt.Println("ReplicaSets", dr.replicaSets)

Check failure on line 129 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
fmt.Println("Pods", dr.Pods)

Check failure on line 130 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used

condition := metav1.Condition{
Type: "Available",
Status: metav1.ConditionTrue,
LastTransitionTime: metav1.Time{
Time: time.Now().UTC(),
},
ObservedGeneration: gObj.GetGeneration(),
}

var (
ready = len(dr.ReadyPods())
updated = len(dr.UpdatedPods())
available = len(dr.ReadyPods())
)
fmt.Println("ReadyPods", ready)

Check failure on line 146 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
fmt.Println("UpdatedPods", updated)

Check failure on line 147 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
fmt.Println("AvailableReplicas", available)

Check failure on line 148 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
gObj.SetReplicas(len(dr.Pods))
gObj.SetUpdatedReplicas(updated)
gObj.SetAvailableReplicas(ready)
gObj.SetUnavailableReplicas(len(dr.Pods) - ready)

switch {
case dr.deployment == nil:
condition.Reason = "DeploymentNotFound"
condition.Status = metav1.ConditionFalse
case ready < gObj.ExpectedReplicas():
switch {
case available == 0:
condition.Reason = "NoPodReady"
condition.Status = metav1.ConditionFalse
default:
condition.Reason = "SomePodsNotReady"
condition.Status = "Degraded"
}
}

var messages []string
for _, p := range dr.Pods {
if m := p.Message(); m != "" {
messages = append(messages, fmt.Sprintf("pod %s: %s", p.Name, m))
}
}

condition.Message = strings.Join(messages, "\n")
fmt.Println("Condition status", condition)

Check failure on line 177 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
fmt.Println("Condition mesasage", condition.Message)

Check failure on line 178 in internal/pkg/status/deployment_reporter.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go

declaration "Println" from package "fmt" shouldn't be used
return condition
}
20 changes: 20 additions & 0 deletions internal/pkg/status/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package status

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// GoverningObject is an interface that represents a Kubernetes object that can be used to report the status of a StatefulSet or Deployment.
type GoverningObject interface {
metav1.Object
ExpectedReplicas() int
SetReplicas(int)
SetUpdatedReplicas(int)
SetAvailableReplicas(int)
SetUnavailableReplicas(int)
}

type Reporter interface {
Update(GoverningObject) metav1.Condition
}

var _ Reporter = &StatefulSetReporter{}
var _ Reporter = &DeploymentReporter{}
40 changes: 40 additions & 0 deletions internal/pkg/status/pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package status

import v1 "k8s.io/api/core/v1"

// Pod is an alias for the Kubernetes v1.Pod type.
type Pod v1.Pod

// Ready returns true if the pod is ready.
func (p *Pod) Ready() bool {
if p.Status.Phase != v1.PodRunning {
return false
}

for _, cond := range p.Status.Conditions {
if cond.Type != v1.PodReady {
continue
}
return cond.Status == v1.ConditionTrue
}

return false
}

// Message returns a human-readable and terse message about the state of the pod.
func (p *Pod) Message() string {
for _, condType := range []v1.PodConditionType{
v1.PodScheduled, // Check first that the pod is scheduled.
v1.PodInitialized, // Then that init containers have been started successfully.
v1.ContainersReady, // Then that all containers are ready.
v1.PodReady, // And finally that the pod is ready.
} {
for _, cond := range p.Status.Conditions {
if cond.Type == condType && cond.Status == v1.ConditionFalse {
return cond.Message
}
}
}

return ""
}
Loading

0 comments on commit b07cdec

Please sign in to comment.