Skip to content

Commit

Permalink
healthcheck: distinguish between agent/server/client
Browse files Browse the repository at this point in the history
This is the first step towards formalizing the concept of "agent",
"server", and "client" nodes.
  • Loading branch information
chrboe committed Apr 3, 2023
1 parent 99743e7 commit e0b2507
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 3 deletions.
2 changes: 2 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Client struct {
Iscsi *ISCSIService
Nfs *NFSService
NvmeOf *NvmeOfService
Status *StatusService
}

type clientError string
Expand Down Expand Up @@ -81,6 +82,7 @@ func NewClient(options ...Option) (*Client, error) {
c.Iscsi = &ISCSIService{c}
c.Nfs = &NFSService{c}
c.NvmeOf = &NvmeOfService{c}
c.Status = &StatusService{c}
return c, nil
}

Expand Down
19 changes: 19 additions & 0 deletions client/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package client

import (
"context"
)

type StatusService struct {
client *Client
}

type Status struct {
Status string `json:"status"`
}

func (s *StatusService) Get(ctx context.Context) (*Status, error) {
var status *Status
_, err := s.client.doGET(ctx, "/api/v2/status", &status)
return status, err
}
21 changes: 20 additions & 1 deletion cmd/check_health.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,30 @@ import (
)

func checkHealthCommand() *cobra.Command {
var mode string
cmd := &cobra.Command{
Use: "check-health",
Short: "Check if all requirements and dependencies are met on the current system",
Long: `Check if all requirements and dependencies are met on the current system.
The "mode" argument can be used to specify the type of node this
system is intended to be used as.
An "agent" node is responsible for actually running the highly available storage
endpoint (for example, an iSCSI target). Thus, the health check ensures the
current system is correctly configured to host the different kinds of storage
protocols supported by LINSTOR Gateway.
A "server" node acts as a relay between the client and the LINSTOR controller.
The only requirement is that the LINSTOR controller can be reached.
A "client" node interacts with the LINSTOR Gateway API. Its only requirement is
that a LINSTOR Gateway server can be reached.
`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
controllers := viper.GetStringSlice("linstor.controllers")
err := healthcheck.CheckRequirements(controllers)
err := healthcheck.CheckRequirements(mode, controllers, cli)
if err != nil {
fmt.Println()
log.Fatalf("Health check failed: %v", err)
Expand All @@ -23,6 +41,7 @@ func checkHealthCommand() *cobra.Command {
}
cmd.Flags().StringSlice("controllers", nil, "List of LINSTOR controllers to try to connect to (default from $LS_CONTROLLERS, or localhost:3370)")
viper.BindPFlag("linstor.controllers", cmd.Flags().Lookup("controllers"))
cmd.Flags().StringVarP(&mode, "mode", "m", "agent", `Which type of node to check requirements for. Can be "agent", "server", or "client"`)

return cmd
}
38 changes: 38 additions & 0 deletions pkg/healthcheck/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package healthcheck

import (
"context"
"fmt"
"github.com/LINBIT/linstor-gateway/client"
"github.com/fatih/color"
"strings"
"time"
)

type checkGatewayServerConnection struct {
cli *client.Client
}

func (c *checkGatewayServerConnection) check(bool) error {
ctx, done := context.WithTimeout(context.Background(), 5*time.Second)
defer done()
status, err := c.cli.Status.Get(ctx)
if err != nil {
return fmt.Errorf("failed to connect to server: %w", err)
}
if status == nil {
return fmt.Errorf("received nil status from server")
}
if status.Status != "ok" {
return fmt.Errorf("received invalid status from server: %q", status.Status)
}
return nil
}

func (c *checkGatewayServerConnection) format(err error) string {
var b strings.Builder
fmt.Fprintf(&b, " %s The LINSTOR Gateway server cannot be reached from this node\n", color.RedString("✗"))
fmt.Fprintf(&b, " %s\n\n", err.Error())
fmt.Fprintf(&b, " Make sure the %s command line option points to a running LINSTOR Gateway server.\n", bold("--connect"))
return b.String()
}
53 changes: 51 additions & 2 deletions pkg/healthcheck/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package healthcheck
import (
"errors"
"fmt"
"github.com/LINBIT/linstor-gateway/client"
"github.com/fatih/color"
)

Expand Down Expand Up @@ -54,11 +55,10 @@ func contains(haystack []string, needle string) bool {
return false
}

func CheckRequirements(controllers []string) error {
func checkAgent() error {
errs := 0
err := category(
"LINSTOR",
&checkLinstor{controllers},
&checkFileWhitelist{},
)
if err != nil {
Expand Down Expand Up @@ -107,3 +107,52 @@ func CheckRequirements(controllers []string) error {
}
return nil
}

func checkServer(controllers []string) error {
errs := 0
err := category(
"LINSTOR",
&checkLinstor{controllers},
)
if err != nil {
errs++
}
if errs > 0 {
return fmt.Errorf("found %d issues", errs)
}
return nil
}

func checkClient(cli *client.Client) error {
errs := 0
err := category(
"Server Connection",
&checkGatewayServerConnection{cli},
)
if err != nil {
errs++
}
if errs > 0 {
return fmt.Errorf("found %d issues", errs)
}
return nil
}

func CheckRequirements(mode string, controllers []string, cli *client.Client) error {
doPrint := func() {
fmt.Printf("Checking %s requirements.\n\n", bold(mode))
}
switch mode {
case "agent":
doPrint()
return checkAgent()
case "server":
doPrint()
return checkServer(controllers)
case "client":
doPrint()
return checkClient(cli)
default:
return fmt.Errorf("unknown mode %q. Expected \"agent\", \"server\", or \"client\"", mode)
}
}

0 comments on commit e0b2507

Please sign in to comment.