Skip to content

Commit

Permalink
Move all healthcheck-related code to pkg/healthcheck (#1492)
Browse files Browse the repository at this point in the history
* Move all healthcheck-related code to pkg/healthcheck
* Fix failed check formatting
* Better version check wording

Signed-off-by: Kevin Lingerfelt <[email protected]>
  • Loading branch information
klingerf authored Aug 20, 2018
1 parent b8434d6 commit e97be1f
Show file tree
Hide file tree
Showing 15 changed files with 532 additions and 725 deletions.
112 changes: 30 additions & 82 deletions cli/cmd/check.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
package cmd

import (
"errors"
"fmt"
"io"
"os"

"github.com/linkerd/linkerd2/controller/api/public"
healthcheckPb "github.com/linkerd/linkerd2/controller/gen/common/healthcheck"
pb "github.com/linkerd/linkerd2/controller/gen/public"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/version"
"github.com/spf13/cobra"
)

const (
lineWidth = 80
okStatus = "[ok]"
failStatus = "[FAIL]"
errorStatus = "[ERROR]"
versionCheckURL = "https://versioncheck.linkerd.io/version.json"
lineWidth = 80
okStatus = "[ok]"
failStatus = "[FAIL]"
)

type checkOptions struct {
Expand All @@ -44,33 +36,7 @@ local system, the Linkerd control plane, and connectivity between those. The pro
problems were found.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {

kubeApi, err := k8s.NewAPI(kubeconfigPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error with Kubernetes API: %s\n", err.Error())
statusCheckResultWasError(os.Stdout)
os.Exit(2)
}

var apiClient pb.ApiClient
if apiAddr != "" {
apiClient, err = public.NewInternalClient(controlPlaneNamespace, apiAddr)
} else {
apiClient, err = public.NewExternalClient(controlPlaneNamespace, kubeApi)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error with Linkerd API: %s\n", err.Error())
statusCheckResultWasError(os.Stdout)
os.Exit(2)
}

grpcStatusChecker := healthcheck.NewGrpcStatusChecker(apiClient)
versionStatusChecker := version.NewVersionStatusChecker(versionCheckURL, options.versionOverride, apiClient)

err = checkStatus(os.Stdout, kubeApi, grpcStatusChecker, versionStatusChecker)
if err != nil {
os.Exit(2)
}
configureAndRunChecks(options)
},
}

Expand All @@ -80,59 +46,41 @@ problems were found.`,
return cmd
}

func checkStatus(w io.Writer, checkers ...healthcheck.StatusChecker) error {
prettyPrintResults := func(result *healthcheckPb.CheckResult) {
checkLabel := fmt.Sprintf("%s: %s", result.SubsystemName, result.CheckDescription)
func configureAndRunChecks(options *checkOptions) {
hc := healthcheck.NewHealthChecker()
hc.AddKubernetesAPIChecks(kubeconfigPath)
hc.AddLinkerdAPIChecks(apiAddr, controlPlaneNamespace)
hc.AddLinkerdVersionChecks(options.versionOverride)

success := runChecks(os.Stdout, hc)

fmt.Println("")

if !success {
fmt.Printf("Status check results are %s\n", failStatus)
os.Exit(2)
}

fmt.Printf("Status check results are %s\n", okStatus)
}

func runChecks(w io.Writer, hc *healthcheck.HealthChecker) bool {
prettyPrintResults := func(category, description string, err error) {
checkLabel := fmt.Sprintf("%s: %s", category, description)

filler := ""
lineBreak := "\n"
for i := 0; i < lineWidth-len(checkLabel)-len(okStatus)-len(lineBreak); i++ {
filler = filler + "."
}

switch result.Status {
case healthcheckPb.CheckStatus_OK:
fmt.Fprintf(w, "%s%s%s%s", checkLabel, filler, okStatus, lineBreak)
case healthcheckPb.CheckStatus_FAIL:
fmt.Fprintf(w, "%s%s%s -- %s%s", checkLabel, filler, failStatus, result.FriendlyMessageToUser, lineBreak)
case healthcheckPb.CheckStatus_ERROR:
fmt.Fprintf(w, "%s%s%s -- %s%s", checkLabel, filler, errorStatus, result.FriendlyMessageToUser, lineBreak)
if err != nil {
fmt.Fprintf(w, "%s%s%s -- %s%s", checkLabel, filler, failStatus, err.Error(), lineBreak)
return
}
}

checker := healthcheck.MakeHealthChecker()
for _, c := range checkers {
checker.Add(c)
fmt.Fprintf(w, "%s%s%s%s", checkLabel, filler, okStatus, lineBreak)
}

checkStatus := checker.PerformCheck(prettyPrintResults)

fmt.Fprintln(w, "")

var err error
switch checkStatus {
case healthcheckPb.CheckStatus_OK:
err = statusCheckResultWasOk(w)
case healthcheckPb.CheckStatus_FAIL:
err = statusCheckResultWasFail(w)
case healthcheckPb.CheckStatus_ERROR:
err = statusCheckResultWasError(w)
}

return err
}

func statusCheckResultWasOk(w io.Writer) error {
fmt.Fprintln(w, "Status check results are [ok]")
return nil
}

func statusCheckResultWasFail(w io.Writer) error {
fmt.Fprintln(w, "Status check results are [FAIL]")
return errors.New("failed status check")
}

func statusCheckResultWasError(w io.Writer) error {
fmt.Fprintln(w, "Status check results are [ERROR]")
return errors.New("error during status check")
return hc.RunChecks(prettyPrintResults)
}
36 changes: 11 additions & 25 deletions cli/cmd/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,27 @@ package cmd

import (
"bytes"
"fmt"
"io/ioutil"
"testing"

healthcheckPb "github.com/linkerd/linkerd2/controller/gen/common/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/healthcheck"
)

func TestCheckStatus(t *testing.T) {
t.Run("Prints expected output", func(t *testing.T) {
kubeApi := &k8s.MockKubeApi{}
kubeApi.SelfCheckResultsToReturn = []*healthcheckPb.CheckResult{
{
SubsystemName: k8s.KubeapiSubsystemName,
CheckDescription: k8s.KubeapiClientCheckDescription,
Status: healthcheckPb.CheckStatus_FAIL,
FriendlyMessageToUser: "This should contain instructions for fail",
},
{
SubsystemName: k8s.KubeapiSubsystemName,
CheckDescription: k8s.KubeapiAccessCheckDescription,
Status: healthcheckPb.CheckStatus_OK,
FriendlyMessageToUser: "This shouldn't be printed",
},
{
SubsystemName: k8s.KubeapiSubsystemName,
CheckDescription: k8s.KubeapiVersionCheckDescription,
Status: healthcheckPb.CheckStatus_ERROR,
FriendlyMessageToUser: "This should contain instructions for err",
},
}
hc := healthcheck.NewHealthChecker()
hc.Add("category", "check1", func() error {
return nil
})
hc.Add("category", "check2", func() error {
return fmt.Errorf("This should contain instructions for fail")
})

output := bytes.NewBufferString("")
checkStatus(output, kubeApi)
runChecks(output, hc)

goldenFileBytes, err := ioutil.ReadFile("testdata/status_busy_output.golden")
goldenFileBytes, err := ioutil.ReadFile("testdata/check_output.golden")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
Expand Down
79 changes: 20 additions & 59 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package cmd

import (
"context"
"fmt"
"os"
"regexp"
"strings"
"time"

"github.com/linkerd/linkerd2/controller/api/public"
healthcheckPb "github.com/linkerd/linkerd2/controller/gen/common/healthcheck"
pb "github.com/linkerd/linkerd2/controller/gen/public"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/version"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -74,69 +71,33 @@ func init() {
// checks to determine if the client can successfully connect to the API. If the
// checks fail, then CLI will print an error and exit.
func validatedPublicAPIClient() pb.ApiClient {
client, err := newPublicAPIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot connect to Kubernetes: %s\n", err)
os.Exit(1)
}
hc := healthcheck.NewHealthChecker()
hc.AddKubernetesAPIChecks(kubeconfigPath)
hc.AddLinkerdAPIChecks(apiAddr, controlPlaneNamespace)

var selfCheckWithRetry func() error
selfCheckWithRetry = func() error {
res, err := client.SelfCheck(context.Background(), &healthcheckPb.SelfCheckRequest{})
exitOnError := func(category, description string, err error) {
if err != nil {
return err
}

for _, result := range res.Results {
if result.Status != healthcheckPb.CheckStatus_OK {
// If the control plane can't talk to Prometheus, that's likely a result
// of Prometheus not passing its readiness check yet on startup. In that
// case, print a waiting message and retry.
if result.SubsystemName == public.PromClientSubsystemName {
fmt.Fprintln(os.Stderr, "Waiting for controller to connect to Prometheus")
return selfCheckWithRetry()
}

return fmt.Errorf(result.FriendlyMessageToUser)
var msg string
switch category {
case healthcheck.KubernetesAPICategory:
msg = "Cannot connect to Kubernetes"
case healthcheck.LinkerdAPICategory:
msg = "Cannot connect to Linkerd"
}
}

return nil
}

if err := selfCheckWithRetry(); err != nil {
fmt.Fprintf(os.Stderr, "Cannot connect to Linkerd: %s\n", err)
checkCmd := "linkerd check"
if controlPlaneNamespace != defaultNamespace {
checkCmd += fmt.Sprintf(" --linkerd-namespace %s", controlPlaneNamespace)
}
fmt.Fprintf(os.Stderr, "Validate the install with: %s\n", checkCmd)
os.Exit(1)
}

return client
}
fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err)

// newPublicAPIClient executes status checks to determine if we can connect
// to Kubernetes, and if so returns a new public API client. Otherwise it
// returns an error.
func newPublicAPIClient() (pb.ApiClient, error) {
if apiAddr != "" {
return public.NewInternalClient(controlPlaneNamespace, apiAddr)
}

kubeAPI, err := k8s.NewAPI(kubeconfigPath)
if err != nil {
return nil, err
}
checkCmd := "linkerd check"
if controlPlaneNamespace != defaultNamespace {
checkCmd += fmt.Sprintf(" --linkerd-namespace %s", controlPlaneNamespace)
}
fmt.Fprintf(os.Stderr, "Validate the install with: %s\n", checkCmd)

for _, result := range kubeAPI.SelfCheck() {
if result.Status != healthcheckPb.CheckStatus_OK {
return nil, fmt.Errorf(result.FriendlyMessageToUser)
os.Exit(1)
}
}

return public.NewExternalClient(controlPlaneNamespace, kubeAPI)
hc.RunChecks(exitOnError)
return hc.PublicAPIClient()
}

type proxyConfigOptions struct {
Expand Down
2 changes: 2 additions & 0 deletions cli/cmd/testdata/check_output.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
category: check1...........................................................[ok]
category: check2...........................................................[FAIL] -- This should contain instructions for fail
5 changes: 0 additions & 5 deletions cli/cmd/testdata/status_busy_output.golden

This file was deleted.

Loading

0 comments on commit e97be1f

Please sign in to comment.