Skip to content

Commit

Permalink
Add dashboard to status command. Fail gracefully on error. (#473)
Browse files Browse the repository at this point in the history
  • Loading branch information
mukundansundar authored Sep 28, 2020
1 parent f00cb05 commit 0703152
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 53 deletions.
11 changes: 10 additions & 1 deletion cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ var StatusCmd = &cobra.Command{
Use: "status",
Short: "Shows the Dapr system services (control plane) health status.",
Run: func(cmd *cobra.Command, args []string) {
status, err := kubernetes.Status()
sc, err := kubernetes.NewStatusClient()
if err != nil {
print.FailureStatusEvent(os.Stdout, err.Error())
os.Exit(1)
}
status, err := sc.Status()
if err != nil {
print.FailureStatusEvent(os.Stdout, err.Error())
os.Exit(1)
}
if len(status) == 0 {
print.FailureStatusEvent(os.Stdout, "No status returned. Is Dapr initialized in your cluster?")
os.Exit(1)
}
table, err := gocsv.MarshalString(status)
if err != nil {
print.FailureStatusEvent(os.Stdout, err.Error())
Expand Down
8 changes: 8 additions & 0 deletions pkg/kubernetes/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import (
k8s "k8s.io/client-go/kubernetes"
)

func ListPodsInterface(client k8s.Interface, labelSelector map[string]string) (*core_v1.PodList, error) {
opts := v1.ListOptions{}
if labelSelector != nil {
opts.LabelSelector = labels.FormatLabels(labelSelector)
}
return client.CoreV1().Pods(v1.NamespaceAll).List(opts)
}

func ListPods(client *k8s.Clientset, namespace string, labelSelector map[string]string) (*core_v1.PodList, error) {
opts := v1.ListOptions{}
if labelSelector != nil {
Expand Down
47 changes: 47 additions & 0 deletions pkg/kubernetes/pods_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------

package kubernetes

import (
"testing"

"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)

func TestListPodsInterface(t *testing.T) {
t.Run("empty list pods", func(t *testing.T) {
k8s := fake.NewSimpleClientset()
output, err := ListPodsInterface(k8s, map[string]string{
"test": "test",
})
assert.Nil(t, err, "unexpected error")
assert.NotNil(t, output, "Expected empty list")
assert.Equal(t, 0, len(output.Items), "Expected length 0")
})
t.Run("one matching pod", func(t *testing.T) {
k8s := fake.NewSimpleClientset((&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
Annotations: map[string]string{},
Labels: map[string]string{
"test": "test",
},
},
}))
output, err := ListPodsInterface(k8s, map[string]string{
"test": "test",
})
assert.Nil(t, err, "unexpected error")
assert.NotNil(t, output, "Expected non empty list")
assert.Equal(t, 1, len(output.Items), "Expected length 0")
assert.Equal(t, "test", output.Items[0].Name, "expected name to match")
assert.Equal(t, "test", output.Items[0].Namespace, "expected namespace to match")
})
}
128 changes: 76 additions & 52 deletions pkg/kubernetes/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ package kubernetes

import (
"fmt"
"os"
"strings"
"sync"

"errors"

"github.com/dapr/cli/pkg/age"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/dapr/cli/pkg/print"
k8s "k8s.io/client-go/kubernetes"
)

var (
controlPlaneLabels = []string{"dapr-operator", "dapr-sentry", "dapr-placement", "dapr-sidecar-injector"}
)
var controlPlaneLabels = []string{"dapr-operator", "dapr-sentry", "dapr-placement", "dapr-sidecar-injector", "dapr-dashboard"}

type StatusClient struct {
client k8s.Interface
}

// StatusOutput represents the status of a named Dapr resource.
type StatusOutput struct {
Expand All @@ -30,13 +36,23 @@ type StatusOutput struct {
Created string `csv:"CREATED"`
}

// List status for Dapr resources.
func Status() ([]StatusOutput, error) {
client, err := Client()
// Create a new k8s client for status commands
func NewStatusClient() (*StatusClient, error) {
clientset, err := Client()
if err != nil {
return nil, err
}
return &StatusClient{
client: clientset,
}, nil
}

// List status for Dapr resources.
func (s *StatusClient) Status() ([]StatusOutput, error) {
client := s.client
if client == nil {
return nil, errors.New("kubernetes client not initialized")
}
var wg sync.WaitGroup
wg.Add(len(controlPlaneLabels))

Expand All @@ -45,60 +61,68 @@ func Status() ([]StatusOutput, error) {

for _, lbl := range controlPlaneLabels {
go func(label string) {
p, err := ListPods(client, v1.NamespaceAll, map[string]string{
defer wg.Done()
// Query all namespaces for Dapr pods.
p, err := ListPodsInterface(client, map[string]string{
"app": label,
})
if err == nil {
pod := p.Items[0]
replicas := len(p.Items)
image := pod.Spec.Containers[0].Image
namespace := pod.GetNamespace()
age := age.GetAge(pod.CreationTimestamp.Time)
created := pod.CreationTimestamp.Format("2006-01-02 15:04.05")
version := image[strings.IndexAny(image, ":")+1:]
status := ""

// loop through all replicas and update to Running/Healthy status only if all instances are Running and Healthy
healthy := "False"
running := true

for _, p := range p.Items {
if p.Status.ContainerStatuses[0].State.Waiting != nil {
status = fmt.Sprintf("Waiting (%s)", p.Status.ContainerStatuses[0].State.Waiting.Reason)
} else if pod.Status.ContainerStatuses[0].State.Terminated != nil {
status = "Terminated"
}

if p.Status.ContainerStatuses[0].State.Running == nil {
running = false
break
}

if p.Status.ContainerStatuses[0].Ready {
healthy = "True"
}
if err != nil {
print.WarningStatusEvent(os.Stdout, "Failed to get status for %s: %s", label, err.Error())
return
}

if len(p.Items) == 0 {
return
}
pod := p.Items[0]
replicas := len(p.Items)
image := pod.Spec.Containers[0].Image
namespace := pod.GetNamespace()
age := age.GetAge(pod.CreationTimestamp.Time)
created := pod.CreationTimestamp.Format("2006-01-02 15:04.05")
version := image[strings.IndexAny(image, ":")+1:]
status := ""

// loop through all replicas and update to Running/Healthy status only if all instances are Running and Healthy
healthy := "False"
running := true

for _, p := range p.Items {
if p.Status.ContainerStatuses[0].State.Waiting != nil {
status = fmt.Sprintf("Waiting (%s)", p.Status.ContainerStatuses[0].State.Waiting.Reason)
} else if pod.Status.ContainerStatuses[0].State.Terminated != nil {
status = "Terminated"
}

if running {
status = "Running"
if p.Status.ContainerStatuses[0].State.Running == nil {
running = false

break
}

s := StatusOutput{
Name: label,
Namespace: namespace,
Created: created,
Age: age,
Status: status,
Version: version,
Healthy: healthy,
Replicas: replicas,
if p.Status.ContainerStatuses[0].Ready {
healthy = "True"
}
}

if running {
status = "Running"
}

m.Lock()
statuses = append(statuses, s)
m.Unlock()
s := StatusOutput{
Name: label,
Namespace: namespace,
Created: created,
Age: age,
Status: status,
Version: version,
Healthy: healthy,
Replicas: replicas,
}
wg.Done()

m.Lock()
statuses = append(statuses, s)
m.Unlock()
}(lbl)
}

Expand Down
Loading

0 comments on commit 0703152

Please sign in to comment.