From 84e7018087ddaf004f658cc35c7c8aba7fbbc401 Mon Sep 17 00:00:00 2001 From: malizz Date: Wed, 1 Feb 2023 11:37:30 -0800 Subject: [PATCH] add troubleshoot cli (#16070) * add troubleshoot cli * fix lint issue * fix merge conflict * fix lint issue --- command/registry.go | 4 + .../connect/troubleshoot_connect.go | 109 ++++++++++++++++++ .../connect/troubleshoot_connect_test.go | 16 +++ .../upstreams/troubleshoot_upstreams.go | 102 ++++++++++++++++ .../upstreams/troubleshoot_upstreams_test.go | 16 +++ troubleshoot/connect/troubleshoot_connect.go | 49 ++++++++ 6 files changed, 296 insertions(+) create mode 100644 command/troubleshoot/connect/troubleshoot_connect.go create mode 100644 command/troubleshoot/connect/troubleshoot_connect_test.go create mode 100644 command/troubleshoot/upstreams/troubleshoot_upstreams.go create mode 100644 command/troubleshoot/upstreams/troubleshoot_upstreams_test.go create mode 100644 troubleshoot/connect/troubleshoot_connect.go diff --git a/command/registry.go b/command/registry.go index bf369903e..37ad99106 100644 --- a/command/registry.go +++ b/command/registry.go @@ -116,6 +116,8 @@ import ( tlscacreate "github.com/hashicorp/consul/command/tls/ca/create" tlscert "github.com/hashicorp/consul/command/tls/cert" tlscertcreate "github.com/hashicorp/consul/command/tls/cert/create" + troubleshoot "github.com/hashicorp/consul/command/troubleshoot/connect" + upstreams "github.com/hashicorp/consul/command/troubleshoot/upstreams" "github.com/hashicorp/consul/command/validate" "github.com/hashicorp/consul/command/version" "github.com/hashicorp/consul/command/watch" @@ -240,6 +242,8 @@ func RegisteredCommands(ui cli.Ui) map[string]mcli.CommandFactory { entry{"tls ca create", func(ui cli.Ui) (cli.Command, error) { return tlscacreate.New(ui), nil }}, entry{"tls cert", func(ui cli.Ui) (cli.Command, error) { return tlscert.New(), nil }}, entry{"tls cert create", func(ui cli.Ui) (cli.Command, error) { return tlscertcreate.New(ui), nil }}, + entry{"troubleshoot connect", func(ui cli.Ui) (cli.Command, error) { return troubleshoot.New(ui), nil }}, + entry{"troubleshoot upstreams", func(ui cli.Ui) (cli.Command, error) { return upstreams.New(ui), nil }}, entry{"validate", func(ui cli.Ui) (cli.Command, error) { return validate.New(ui), nil }}, entry{"version", func(ui cli.Ui) (cli.Command, error) { return version.New(ui), nil }}, entry{"watch", func(ui cli.Ui) (cli.Command, error) { return watch.New(ui, MakeShutdownCh()), nil }}, diff --git a/command/troubleshoot/connect/troubleshoot_connect.go b/command/troubleshoot/connect/troubleshoot_connect.go new file mode 100644 index 000000000..a7490a794 --- /dev/null +++ b/command/troubleshoot/connect/troubleshoot_connect.go @@ -0,0 +1,109 @@ +package troubleshoot + +import ( + "flag" + "fmt" + "net" + "os" + + "github.com/hashicorp/consul/command/flags" + troubleshoot "github.com/hashicorp/consul/troubleshoot/connect" + "github.com/mitchellh/cli" +) + +func New(ui cli.Ui) *cmd { + c := &cmd{UI: ui} + c.init() + return c +} + +type cmd struct { + UI cli.Ui + flags *flag.FlagSet + http *flags.HTTPFlags + help string + + // flags + upstream string + adminBind string +} + +func (c *cmd) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + + c.flags.StringVar(&c.upstream, "upstream", os.Getenv("TROUBLESHOOT_CONNECT_UPSTREAM"), "The upstream service that receives the communication. ") + + defaultAdminBind := "localhost:19000" + if adminBind := os.Getenv("ADMIN_BIND"); adminBind != "" { + defaultAdminBind = adminBind + } + c.flags.StringVar(&c.adminBind, "admin-bind", defaultAdminBind, "The address:port that envoy's admin endpoint is on.") + + c.http = &flags.HTTPFlags{} + flags.Merge(c.flags, c.http.ClientFlags()) + flags.Merge(c.flags, c.http.ServerFlags()) + c.help = flags.Usage(help, c.flags) +} + +func (c *cmd) Run(args []string) int { + + if err := c.flags.Parse(args); err != nil { + c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err)) + return 1 + } + + if c.upstream == "" { + c.UI.Error("-upstream service SNI is required") + return 1 + } + + adminAddr, adminPort, err := net.SplitHostPort(c.adminBind) + if err != nil { + c.UI.Error("Invalid Envoy Admin endpoint: " + err.Error()) + return 1 + } + + // Envoy requires IP addresses to bind too when using static so resolve DNS or + // localhost here. + adminBindIP, err := net.ResolveIPAddr("ip", adminAddr) + if err != nil { + c.UI.Error("Failed to resolve admin bind address: " + err.Error()) + return 1 + } + + t, err := troubleshoot.NewTroubleshoot(adminBindIP, adminPort) + if err != nil { + c.UI.Error("error generating troubleshoot client: " + err.Error()) + return 1 + } + output, err := t.RunAllTests(c.upstream) + if err != nil { + c.UI.Error("error running the tests: " + err.Error()) + return 1 + } + c.UI.Output(output) + return 0 +} + +func (c *cmd) Synopsis() string { + return synopsis +} + +func (c *cmd) Help() string { + return c.help +} + +const ( + synopsis = "Troubleshoots service mesh issues" + help = ` +Usage: consul troubleshoot proxy [options] + Connects to local envoy proxy and troubleshoots service mesh communication issues. + Requires an upstream service SNI. + Examples: + $ consul troubleshoot proxy -upstream foo + + where 'foo' is the upstream envoy ID which + can be obtained by running: + $ consul troubleshoot upstreams [options] +` +) diff --git a/command/troubleshoot/connect/troubleshoot_connect_test.go b/command/troubleshoot/connect/troubleshoot_connect_test.go new file mode 100644 index 000000000..9b2fca9f9 --- /dev/null +++ b/command/troubleshoot/connect/troubleshoot_connect_test.go @@ -0,0 +1,16 @@ +package troubleshoot + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestTroubleshootConnectCommand_noTabs(t *testing.T) { + t.Parallel() + + if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { + t.Fatal("help has tabs") + } +} diff --git a/command/troubleshoot/upstreams/troubleshoot_upstreams.go b/command/troubleshoot/upstreams/troubleshoot_upstreams.go new file mode 100644 index 000000000..0e955ae4c --- /dev/null +++ b/command/troubleshoot/upstreams/troubleshoot_upstreams.go @@ -0,0 +1,102 @@ +package upstreams + +import ( + "flag" + "fmt" + "net" + "os" + + "github.com/hashicorp/consul/command/flags" + troubleshoot "github.com/hashicorp/consul/troubleshoot/connect" + "github.com/mitchellh/cli" +) + +func New(ui cli.Ui) *cmd { + c := &cmd{UI: ui} + c.init() + return c +} + +type cmd struct { + UI cli.Ui + flags *flag.FlagSet + http *flags.HTTPFlags + help string + + // flags + adminBind string +} + +func (c *cmd) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + + defaultAdminBind := "localhost:19000" + if adminBind := os.Getenv("ADMIN_BIND"); adminBind != "" { + defaultAdminBind = adminBind + } + c.flags.StringVar(&c.adminBind, "admin-bind", defaultAdminBind, "The address:port that envoy's admin endpoint is on.") + + c.http = &flags.HTTPFlags{} + flags.Merge(c.flags, c.http.ClientFlags()) + flags.Merge(c.flags, c.http.ServerFlags()) + c.help = flags.Usage(help, c.flags) +} + +func (c *cmd) Run(args []string) int { + + if err := c.flags.Parse(args); err != nil { + c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err)) + return 1 + } + + adminAddr, adminPort, err := net.SplitHostPort(c.adminBind) + if err != nil { + c.UI.Error("Invalid Envoy Admin endpoint: " + err.Error()) + return 1 + } + + // Envoy requires IP addresses to bind too when using static so resolve DNS or + // localhost here. + adminBindIP, err := net.ResolveIPAddr("ip", adminAddr) + if err != nil { + c.UI.Error("Failed to resolve admin bind address: " + err.Error()) + return 1 + } + + t, err := troubleshoot.NewTroubleshoot(adminBindIP, adminPort) + if err != nil { + c.UI.Error("error generating troubleshoot client: " + err.Error()) + return 1 + } + upstreams, err := t.GetUpstreams() + if err != nil { + c.UI.Error("error calling GetUpstreams: " + err.Error()) + return 1 + } + + for _, u := range upstreams { + c.UI.Output(u) + } + return 0 +} + +func (c *cmd) Synopsis() string { + return synopsis +} + +func (c *cmd) Help() string { + return c.help +} + +const ( + synopsis = "Troubleshoots service mesh issues" + help = ` +Usage: consul troubleshoot upstreams [options] + + Connects to local Envoy and lists upstream service envoy IDs. + This command is used in combination with + 'consul troubleshoot connect' to diagnose issues in Consul service mesh. + Examples: + $ consul troubleshoot upstreams +` +) diff --git a/command/troubleshoot/upstreams/troubleshoot_upstreams_test.go b/command/troubleshoot/upstreams/troubleshoot_upstreams_test.go new file mode 100644 index 000000000..e36f5c996 --- /dev/null +++ b/command/troubleshoot/upstreams/troubleshoot_upstreams_test.go @@ -0,0 +1,16 @@ +package upstreams + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestTroubleshootUpstreamsCommand_noTabs(t *testing.T) { + t.Parallel() + + if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { + t.Fatal("help has tabs") + } +} diff --git a/troubleshoot/connect/troubleshoot_connect.go b/troubleshoot/connect/troubleshoot_connect.go new file mode 100644 index 000000000..1421bc8f0 --- /dev/null +++ b/troubleshoot/connect/troubleshoot_connect.go @@ -0,0 +1,49 @@ +package troubleshoot + +import ( + "fmt" + "net" + + envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" + + "github.com/hashicorp/consul/api" +) + +type Troubleshoot struct { + client *api.Client + envoyAddr net.IPAddr + envoyAdminPort string + + TroubleshootInfo +} + +type TroubleshootInfo struct { + envoyClusters *envoy_admin_v3.Clusters + envoyConfigDump *envoy_admin_v3.ConfigDump + envoyCerts *envoy_admin_v3.Certificates + envoyStats EnvoyStats +} + +type EnvoyStats []envoy_admin_v3.SimpleMetric + +func NewTroubleshoot(envoyIP *net.IPAddr, envoyPort string) (*Troubleshoot, error) { + cfg := api.DefaultConfig() + c, err := api.NewClient(cfg) + if err != nil { + return nil, err + } + return &Troubleshoot{ + client: c, + envoyAddr: *envoyIP, + envoyAdminPort: envoyPort, + }, nil +} + +func (t *Troubleshoot) RunAllTests(upstreamSNI string) (string, error) { + return "", fmt.Errorf("not implemented") +} + +func (t *Troubleshoot) GetUpstreams() ([]string, error) { + + return nil, fmt.Errorf("not implemented") +}