mirror of https://github.com/linkerd/linkerd2.git
164 lines
3.9 KiB
Go
164 lines
3.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/linkerd/linkerd2/cli/table"
|
|
"github.com/linkerd/linkerd2/controller/api/public"
|
|
pb "github.com/linkerd/linkerd2/controller/gen/public"
|
|
"github.com/linkerd/linkerd2/pkg/k8s"
|
|
"github.com/spf13/cobra"
|
|
corev1 "k8s.io/api/core/v1"
|
|
)
|
|
|
|
type (
|
|
gatewaysOptions struct {
|
|
gatewayNamespace string
|
|
clusterName string
|
|
timeWindow string
|
|
}
|
|
)
|
|
|
|
func newGatewaysCommand() *cobra.Command {
|
|
|
|
opts := gatewaysOptions{}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "gateways",
|
|
Short: "Display stats information about the gateways in target clusters",
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
req := &pb.GatewaysRequest{
|
|
RemoteClusterName: opts.clusterName,
|
|
GatewayNamespace: opts.gatewayNamespace,
|
|
TimeWindow: opts.timeWindow,
|
|
}
|
|
|
|
k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client, err := public.NewExternalClient(cmd.Context(), controlPlaneNamespace, k8sAPI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := requestGatewaysFromAPI(client, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
renderGateways(resp.GetOk().GatewaysTable.Rows, stdout)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&opts.clusterName, "cluster-name", "", "the name of the target cluster")
|
|
cmd.Flags().StringVar(&opts.gatewayNamespace, "gateway-namespace", "", "the namespace in which the gateway resides on the target cluster")
|
|
cmd.Flags().StringVarP(&opts.timeWindow, "time-window", "t", "1m", "Time window (for example: \"15s\", \"1m\", \"10m\", \"1h\"). Needs to be at least 15s.")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func requestGatewaysFromAPI(client pb.ApiClient, req *pb.GatewaysRequest) (*pb.GatewaysResponse, error) {
|
|
resp, err := client.Gateways(context.Background(), req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Gateways API error: %v", err)
|
|
}
|
|
if e := resp.GetError(); e != nil {
|
|
return nil, fmt.Errorf("Gateways API response error: %v", e.Error)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func renderGateways(rows []*pb.GatewaysTable_Row, w io.Writer) {
|
|
t := buildGatewaysTable()
|
|
t.Data = []table.Row{}
|
|
for _, row := range rows {
|
|
row := row // Copy to satisfy golint.
|
|
t.Data = append(t.Data, gatewaysRowToTableRow(row))
|
|
}
|
|
t.Render(w)
|
|
}
|
|
|
|
var (
|
|
clusterNameHeader = "CLUSTER"
|
|
aliveHeader = "ALIVE"
|
|
pairedServicesHeader = "NUM_SVC"
|
|
latencyP50Header = "LATENCY_P50"
|
|
latencyP95Header = "LATENCY_P95"
|
|
latencyP99Header = "LATENCY_P99"
|
|
)
|
|
|
|
func buildGatewaysTable() table.Table {
|
|
columns := []table.Column{
|
|
table.Column{
|
|
Header: clusterNameHeader,
|
|
Width: 7,
|
|
Flexible: true,
|
|
LeftAlign: true,
|
|
},
|
|
table.Column{
|
|
Header: aliveHeader,
|
|
Width: 5,
|
|
Flexible: true,
|
|
LeftAlign: true,
|
|
},
|
|
table.Column{
|
|
Header: pairedServicesHeader,
|
|
Width: 9,
|
|
},
|
|
table.Column{
|
|
Header: latencyP50Header,
|
|
Width: 11,
|
|
},
|
|
table.Column{
|
|
Header: latencyP95Header,
|
|
Width: 11,
|
|
},
|
|
table.Column{
|
|
Header: latencyP99Header,
|
|
Width: 11,
|
|
},
|
|
}
|
|
t := table.NewTable(columns, []table.Row{})
|
|
t.Sort = []int{0, 1} // Sort by namespace, then name.
|
|
return t
|
|
}
|
|
|
|
func gatewaysRowToTableRow(row *pb.GatewaysTable_Row) []string {
|
|
valueOrPlaceholder := func(value string) string {
|
|
if row.Alive {
|
|
return value
|
|
}
|
|
return "-"
|
|
}
|
|
|
|
alive := "False"
|
|
|
|
if row.Alive {
|
|
alive = "True"
|
|
}
|
|
return []string{
|
|
row.ClusterName,
|
|
alive,
|
|
fmt.Sprint(row.PairedServices),
|
|
valueOrPlaceholder(fmt.Sprintf("%dms", row.LatencyMsP50)),
|
|
valueOrPlaceholder(fmt.Sprintf("%dms", row.LatencyMsP95)),
|
|
valueOrPlaceholder(fmt.Sprintf("%dms", row.LatencyMsP99)),
|
|
}
|
|
|
|
}
|
|
|
|
func extractGatewayPort(gateway *corev1.Service) (uint32, error) {
|
|
for _, port := range gateway.Spec.Ports {
|
|
if port.Name == k8s.GatewayPortName {
|
|
return uint32(port.Port), nil
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("gateway service %s has no gateway port named %s", gateway.Name, k8s.GatewayPortName)
|
|
}
|