From 36c010619d75e6c9d2f37647193b4679816afaa4 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Fri, 27 Sep 2019 12:52:08 +0200 Subject: [PATCH] Cleanup client options Signed-off-by: Ondrej Fabry --- api/common.go | 2 +- api/types/client.go | 2 +- cmd/agentctl/cli/cli.go | 10 +-- cmd/agentctl/cli/flags.go | 6 +- cmd/agentctl/cli/opts.go | 130 ---------------------------- cmd/agentctl/client/api.go | 2 +- cmd/agentctl/client/client.go | 118 +++++++++---------------- cmd/agentctl/client/http.go | 6 +- cmd/agentctl/client/model.go | 7 +- cmd/agentctl/client/options.go | 18 +--- cmd/agentctl/client/scheduler.go | 2 +- cmd/agentctl/commands/commands.go | 62 ++++++++++++++ cmd/agentctl/commands/errors.go | 1 + cmd/agentctl/commands/root.go | 81 ++++-------------- cmd/agentctl/commands/status.go | 2 +- cmd/agentctl/commands/values.go | 137 ++++++++++++++++++++++++++++++ cmd/agentctl/commands/vpp.go | 10 ++- tests/e2e/e2e_test.go | 5 +- 18 files changed, 288 insertions(+), 313 deletions(-) delete mode 100644 cmd/agentctl/cli/opts.go create mode 100644 cmd/agentctl/commands/commands.go create mode 100644 cmd/agentctl/commands/values.go diff --git a/api/common.go b/api/common.go index 09ff6dfef4..14ecc9cb5c 100644 --- a/api/common.go +++ b/api/common.go @@ -15,5 +15,5 @@ package api const ( - DefaultVersion = "0.1" + DefaultVersion = "" ) diff --git a/api/types/client.go b/api/types/client.go index 4dc5deb1b9..fc0ef69e16 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -33,6 +33,6 @@ type SchedulerDumpOptions struct { KeyPrefix string View string } -type SchedulerStatusOptions struct { +type SchedulerValuesOptions struct { KeyPrefix string } diff --git a/cmd/agentctl/cli/cli.go b/cmd/agentctl/cli/cli.go index 96e3276a7e..d04ba26dac 100644 --- a/cmd/agentctl/cli/cli.go +++ b/cmd/agentctl/cli/cli.go @@ -18,17 +18,15 @@ import ( "context" "fmt" "io" - "net" "net/http" "runtime" "github.com/docker/cli/cli/streams" "github.com/docker/docker/pkg/term" - "github.com/ligato/cn-infra/logging" - "github.com/sirupsen/logrus" "github.com/ligato/cn-infra/db/keyval" "github.com/ligato/cn-infra/db/keyval/kvproto" + "github.com/ligato/cn-infra/logging" "github.com/ligato/vpp-agent/api" "github.com/ligato/vpp-agent/cmd/agentctl/client" @@ -169,8 +167,7 @@ func (cli *AgentCli) Initialize(opts *ClientOptions, ops ...InitializeOpt) error } if opts.Debug { debug.Enable() - logrus.SetLevel(logrus.DebugLevel) - logging.DefaultLogger.SetLevel(logging.DebugLevel) + SetLogLevel("debug") } else { SetLogLevel(opts.LogLevel) } @@ -194,10 +191,9 @@ func newAPIClient(opts *ClientOptions) (client.APIClient, error) { // No proxy Transport: &http.Transport{}, } - grpcAddr := net.JoinHostPort(opts.AgentHost, opts.PortGRPC) clientOpts := []client.Opt{ client.WithHTTPClient(httpClient), - client.WithGRPCAddr(grpcAddr), + client.WithHost(opts.AgentHost), client.WithEtcdEndpoints(opts.Endpoints), client.WithServiceLabel(opts.ServiceLabel), } diff --git a/cmd/agentctl/cli/flags.go b/cmd/agentctl/cli/flags.go index f38f2b70f0..89b7c0d37b 100644 --- a/cmd/agentctl/cli/flags.go +++ b/cmd/agentctl/cli/flags.go @@ -6,9 +6,11 @@ import ( "strings" "github.com/docker/go-connections/tlsconfig" - "github.com/ligato/cn-infra/logging" "github.com/sirupsen/logrus" "github.com/spf13/pflag" + + "github.com/ligato/cn-infra/logging" + "github.com/ligato/vpp-agent/cmd/agentctl/client" ) const ( @@ -26,7 +28,7 @@ var ( func init() { if agentHost == "" { - agentHost = defaultAgentHost + agentHost = client.DefaultAgentHost } if len(etcdEndpoints) == 0 || etcdEndpoints[0] == "" { etcdEndpoints = []string{defaultEtcdEndpoint} diff --git a/cmd/agentctl/cli/opts.go b/cmd/agentctl/cli/opts.go deleted file mode 100644 index 54c42d88ba..0000000000 --- a/cmd/agentctl/cli/opts.go +++ /dev/null @@ -1,130 +0,0 @@ -package cli - -import ( - "fmt" - "net" - "net/url" - "strconv" - "strings" -) - -const DefaultAgentHost = "localhost" - -var ( - DefaultGRPCPort = 9111 - DefaultHTTPPort = 9191 - DefaultTLSHTTPPort = 9192 - DefaultUnixSocket = "/var/run/agent.sock" - DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultAgentHost, DefaultHTTPPort) - DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultAgentHost, DefaultTLSHTTPPort) - DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) -) - -// ParseHost and set defaults for a agent host string -func ParseHost(defaultToTLS bool, val string) (string, error) { - host := strings.TrimSpace(val) - if host == "" { - if defaultToTLS { - host = DefaultTLSHost - } else { - host = DefaultHost - } - } else { - var err error - host, err = parseAgentHost(host) - if err != nil { - return val, err - } - } - return host, nil -} - -// parseAgentHost parses the specified address and returns an address that will be used as the host. -// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. -func parseAgentHost(addr string) (string, error) { - addrParts := strings.SplitN(addr, "://", 2) - if len(addrParts) == 1 && addrParts[0] != "" { - addrParts = []string{"tcp", addrParts[0]} - } - - switch addrParts[0] { - case "tcp": - return ParseTCPAddr(addrParts[1], DefaultTCPHost) - case "unix": - return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket) - case "fd": - return addr, nil - case "ssh": - return addr, nil - default: - return "", fmt.Errorf("Invalid bind address format: %s", addr) - } -} - -// parseSimpleProtoAddr parses and validates that the specified address is a valid -// socket address for simple protocols like unix and npipe. It returns a formatted -// socket address, either using the address parsed from addr, or the contents of -// defaultAddr if addr is a blank string. -func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) { - addr = strings.TrimPrefix(addr, proto+"://") - if strings.Contains(addr, "://") { - return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr) - } - if addr == "" { - addr = defaultAddr - } - return fmt.Sprintf("%s://%s", proto, addr), nil -} - -// ParseTCPAddr parses and validates that the specified address is a valid TCP -// address. It returns a formatted TCP address, either using the address parsed -// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. -// tryAddr is expected to have already been Trim()'d -// defaultAddr must be in the full `tcp://host:port` form -func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { - if tryAddr == "" || tryAddr == "tcp://" { - return defaultAddr, nil - } - addr := strings.TrimPrefix(tryAddr, "tcp://") - if strings.Contains(addr, "://") || addr == "" { - return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr) - } - - defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://") - defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr) - if err != nil { - return "", err - } - // url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but - // not 1.4. See https://github.com/golang/go/issues/12200 and - // https://github.com/golang/go/issues/6530. - if strings.HasSuffix(addr, "]:") { - addr += defaultPort - } - - u, err := url.Parse("tcp://" + addr) - if err != nil { - return "", err - } - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - // try port addition once - host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort)) - } - if err != nil { - return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) - } - - if host == "" { - host = defaultHost - } - if port == "" { - port = defaultPort - } - p, err := strconv.Atoi(port) - if err != nil && p == 0 { - return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) - } - - return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil -} diff --git a/cmd/agentctl/client/api.go b/cmd/agentctl/client/api.go index 909d59139e..246a3a542a 100644 --- a/cmd/agentctl/client/api.go +++ b/cmd/agentctl/client/api.go @@ -48,7 +48,7 @@ type ModelAPIClient interface { // SchedulerAPIClient defines API client methods for the scheduler type SchedulerAPIClient interface { SchedulerDump(ctx context.Context, opts types.SchedulerDumpOptions) ([]api.KVWithMetadata, error) - SchedulerStatus(ctx context.Context, opts types.SchedulerStatusOptions) ([]*api.BaseValueStatus, error) + SchedulerValues(ctx context.Context, opts types.SchedulerValuesOptions) ([]*api.BaseValueStatus, error) } // VppAPIClient diff --git a/cmd/agentctl/client/client.go b/cmd/agentctl/client/client.go index 9c30f9c2f3..8aad40e3bf 100644 --- a/cmd/agentctl/client/client.go +++ b/cmd/agentctl/client/client.go @@ -16,7 +16,6 @@ package client import ( "context" - "crypto/tls" "fmt" "net/http" "net/url" @@ -25,29 +24,27 @@ import ( "time" "github.com/coreos/etcd/clientv3" - "github.com/docker/go-connections/sockets" + "google.golang.org/grpc" + "github.com/ligato/cn-infra/datasync" "github.com/ligato/cn-infra/db/keyval" "github.com/ligato/cn-infra/db/keyval/etcd" "github.com/ligato/cn-infra/logging" "github.com/ligato/cn-infra/logging/logrus" "github.com/ligato/cn-infra/servicelabel" - "github.com/ligato/vpp-agent/pkg/debug" - - "google.golang.org/grpc" "github.com/ligato/vpp-agent/api" "github.com/ligato/vpp-agent/api/genericmanager" "github.com/ligato/vpp-agent/api/types" "github.com/ligato/vpp-agent/client" "github.com/ligato/vpp-agent/client/remoteclient" + "github.com/ligato/vpp-agent/pkg/debug" ) -const ( - DefaultAgentHost = "tcp://localhost:9191" - - defaultProto = "http" - defaultAddr = "localhost:9191" +var ( + DefaultAgentHost = "localhost" + DefaultPortGRPC = 9111 + DefaultPortHTTP = 9191 ) var _ APIClient = (*Client)(nil) @@ -61,12 +58,14 @@ type Client struct { addr string basePath string - grpcClient *grpc.ClientConn grpcAddr string - httpClient *http.Client + httpAddr string endpoints []string serviceLabel string + grpcClient *grpc.ClientConn + httpClient *http.Client + customHTTPHeaders map[string]string version string manualOverride bool @@ -79,67 +78,21 @@ func NewClient(host string) (*Client, error) { } func NewClientWithOpts(ops ...Opt) (*Client, error) { - httpClient, err := defaultHTTPClient(DefaultAgentHost) - if err != nil { - return nil, err - } c := &Client{ - host: DefaultAgentHost, - //version: api.DefaultVersion, - httpClient: httpClient, - proto: defaultProto, - addr: defaultAddr, + host: DefaultAgentHost, + version: api.DefaultVersion, + httpClient: &http.Client{}, + proto: "tcp", + scheme: "http", } for _, op := range ops { if err := op(c); err != nil { return nil, err } } - - if _, ok := c.httpClient.Transport.(http.RoundTripper); !ok { - return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.httpClient.Transport) - } - if c.scheme == "" { - c.scheme = "http" - - tlsConfig := resolveTLSConfig(c.httpClient.Transport) - if tlsConfig != nil { - // TODO(stevvooe): This isn't really the right way to write clients in Go. - // `NewClient` should probably only take an `*http.Client` and work from there. - // Unfortunately, the model of having a host-ish/url-thingy as the connection - // string has us confusing protocol and transport layers. We continue doing - // this to avoid breaking existing clients but this should be addressed. - c.scheme = "https" - } - } return c, nil } -// resolveTLSConfig attempts to resolve the TLS configuration from the -// RoundTripper. -func resolveTLSConfig(transport http.RoundTripper) *tls.Config { - switch tr := transport.(type) { - case *http.Transport: - return tr.TLSClientConfig - default: - return nil - } -} - -func defaultHTTPClient(host string) (*http.Client, error) { - u, err := ParseHostURL(host) - if err != nil { - return nil, err - } - transport := new(http.Transport) - if err := sockets.ConfigureTransport(transport, u.Scheme, u.Host); err != nil { - return nil, err - } - return &http.Client{ - Transport: transport, - }, nil -} - func (c *Client) ConfigClient() (client.ConfigClient, error) { conn, err := c.GRPCConn() if err != nil { @@ -163,27 +116,13 @@ func (c *Client) Close() error { t.CloseIdleConnections() } if c.grpcClient != nil { - c.grpcClient.Close() + if err := c.grpcClient.Close(); err != nil { + return err + } } return nil } -// getAPIPath returns the versioned request path to call the api. -// It appends the query parameters to the path if they are not empty. -func (c *Client) getAPIPath(ctx context.Context, p string, query url.Values) string { - var apiPath string - if c.negotiateVersion && !c.negotiated { - c.NegotiateAPIVersion(ctx) - } - if c.version != "" { - v := strings.TrimPrefix(c.version, "v") - apiPath = path.Join(c.basePath, "/v"+v, p) - } else { - apiPath = path.Join(c.basePath, p) - } - return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String() -} - func (c *Client) GRPCConn() (*grpc.ClientConn, error) { if c.grpcClient == nil { logging.Debugf("dialing grpc address: %v", c.grpcAddr) @@ -204,6 +143,9 @@ func (c *Client) HTTPClient() *http.Client { // ParseHostURL parses a url string, validates the string is a host url, and // returns the parsed URL func ParseHostURL(host string) (*url.URL, error) { + if !strings.Contains(host, "://") { + host = "tcp://" + host + } protoAddrParts := strings.SplitN(host, "://", 2) if len(protoAddrParts) == 1 { return nil, fmt.Errorf("unable to parse agent host `%s`", host) @@ -225,6 +167,22 @@ func ParseHostURL(host string) (*url.URL, error) { }, nil } +// getAPIPath returns the versioned request path to call the api. +// It appends the query parameters to the path if they are not empty. +func (c *Client) getAPIPath(ctx context.Context, p string, query url.Values) string { + var apiPath string + if c.negotiateVersion && !c.negotiated { + c.NegotiateAPIVersion(ctx) + } + if c.version != "" { + v := strings.TrimPrefix(c.version, "v") + apiPath = path.Join(c.basePath, "/v"+v, p) + } else { + apiPath = path.Join(c.basePath, p) + } + return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String() +} + func (c *Client) NegotiateAPIVersion(ctx context.Context) { if !c.manualOverride { ping, _ := c.Ping(ctx) diff --git a/cmd/agentctl/client/http.go b/cmd/agentctl/client/http.go index b629de9ac7..b70e6c9d16 100644 --- a/cmd/agentctl/client/http.go +++ b/cmd/agentctl/client/http.go @@ -78,12 +78,12 @@ func (c *Client) buildRequest(method, path string, body io.Reader, headers heade req = c.addHeaders(req, headers) if c.proto == "unix" || c.proto == "npipe" { - // For local communications, it doesn't matter what the host is. We just - // need a valid and meaningful host name. (See #189) + // For local communications, it doesn't matter what the host is. + // We just need a valid and meaningful host name. req.Host = "ligato-agent" } - req.URL.Host = c.addr + req.URL.Host = c.httpAddr req.URL.Scheme = c.scheme if expectedPayload && req.Header.Get("Content-Type") == "" { diff --git a/cmd/agentctl/client/model.go b/cmd/agentctl/client/model.go index 8856dcc450..b2bb5851cb 100644 --- a/cmd/agentctl/client/model.go +++ b/cmd/agentctl/client/model.go @@ -11,6 +11,7 @@ import ( "github.com/ligato/vpp-agent/api/genericmanager" "github.com/ligato/vpp-agent/api/types" + "github.com/ligato/vpp-agent/pkg/debug" ) func (c *Client) ModelList(ctx context.Context, opts types.ModelListOptions) ([]types.Model, error) { @@ -24,8 +25,10 @@ func (c *Client) ModelList(ctx context.Context, opts types.ModelListOptions) ([] } logrus.Debugf("retrieved %d known models", len(knownModels)) - for _, m := range knownModels { - logrus.Debug(proto.CompactTextString(&m)) + if debug.IsEnabledFor("models") { + for _, m := range knownModels { + logrus.Debug(proto.CompactTextString(&m)) + } } allModels := convertModels(knownModels) diff --git a/cmd/agentctl/client/options.go b/cmd/agentctl/client/options.go index 8d92a03ad4..19bba77d97 100644 --- a/cmd/agentctl/client/options.go +++ b/cmd/agentctl/client/options.go @@ -1,12 +1,12 @@ package client import ( + "net" "net/http" "os" + "strconv" "time" - "github.com/docker/go-connections/sockets" - "github.com/pkg/errors" "google.golang.org/grpc" ) @@ -41,19 +41,9 @@ func WithHost(host string) Opt { c.host = host c.proto = hostURL.Scheme c.addr = hostURL.Host + c.grpcAddr = net.JoinHostPort(c.host, strconv.Itoa(DefaultPortGRPC)) + c.httpAddr = net.JoinHostPort(c.host, strconv.Itoa(DefaultPortHTTP)) c.basePath = hostURL.Path - if transport, ok := c.httpClient.Transport.(*http.Transport); ok { - return sockets.ConfigureTransport(transport, c.proto, c.addr) - } - return errors.Errorf("cannot apply host to transport: %T", c.httpClient.Transport) - } -} - -func WithGRPCAddr(addr string) Opt { - return func(c *Client) error { - if addr != "" { - c.grpcAddr = addr - } return nil } } diff --git a/cmd/agentctl/client/scheduler.go b/cmd/agentctl/client/scheduler.go index 22084eda92..2af011564d 100644 --- a/cmd/agentctl/client/scheduler.go +++ b/cmd/agentctl/client/scheduler.go @@ -56,7 +56,7 @@ func (c *Client) SchedulerDump(ctx context.Context, opts types.SchedulerDumpOpti return dump, nil } -func (c *Client) SchedulerStatus(ctx context.Context, opts types.SchedulerStatusOptions) ([]*api.BaseValueStatus, error) { +func (c *Client) SchedulerValues(ctx context.Context, opts types.SchedulerValuesOptions) ([]*api.BaseValueStatus, error) { query := url.Values{} query.Set("key-prefix", opts.KeyPrefix) diff --git a/cmd/agentctl/commands/commands.go b/cmd/agentctl/commands/commands.go new file mode 100644 index 0000000000..f33fd0656a --- /dev/null +++ b/cmd/agentctl/commands/commands.go @@ -0,0 +1,62 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/ligato/cn-infra/logging" + "github.com/ligato/vpp-agent/cmd/agentctl/cli" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + // RootName defines default name used for the root command. + RootName = "agentctl" +) + +// NewAgentCli creates new AgentCli with opts and configures log output to error stream. +func NewAgentCli(opts ...cli.AgentCliOption) *cli.AgentCli { + agentCli, err := cli.NewAgentCli(opts...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + logrus.SetOutput(agentCli.Err()) + logging.DefaultLogger.SetOutput(agentCli.Err()) + return agentCli +} + +// NewRootCommand is helper for default initialization process for root command. +// Returs cobra command which is ready to be executed. +func NewRootCommand(agentCli *cli.AgentCli) (*cobra.Command, error) { + root := NewRoot(agentCli) + cmd, err := root.PrepareCommand() + if err != nil { + return nil, err + } + if err := root.Initialize(); err != nil { + return nil, err + } + return cmd, nil +} + +// NewRoot returns new Root using RootName for name. +func NewRoot(agentCli *cli.AgentCli) *Root { + return NewRootNamed(RootName, agentCli) +} + +// AddBaseCommands adds all base commands to cmd. +func AddBaseCommands(cmd *cobra.Command, cli cli.Cli) { + cmd.AddCommand( + NewModelCommand(cli), + NewLogCommand(cli), + NewImportCommand(cli), + NewVppCommand(cli), + NewDumpCommand(cli), + NewKvdbCommand(cli), + NewGenerateCommand(cli), + NewStatusCommand(cli), + NewValuesCommand(cli), + ) +} diff --git a/cmd/agentctl/commands/errors.go b/cmd/agentctl/commands/errors.go index 4b46c6ce05..2dc49f84bb 100644 --- a/cmd/agentctl/commands/errors.go +++ b/cmd/agentctl/commands/errors.go @@ -51,6 +51,7 @@ func (e StatusError) Error() string { return fmt.Sprintf("%s (%d)", e.Status, e.StatusCode) } +// ExitCode returns proper exit code for err or 0 if err is nil. func ExitCode(err error) int { if err == nil { return 0 diff --git a/cmd/agentctl/commands/root.go b/cmd/agentctl/commands/root.go index daa11840d3..0ce047a85b 100644 --- a/cmd/agentctl/commands/root.go +++ b/cmd/agentctl/commands/root.go @@ -27,62 +27,11 @@ import ( "github.com/spf13/pflag" "github.com/ligato/cn-infra/agent" - "github.com/ligato/cn-infra/logging" - - "github.com/ligato/vpp-agent/cmd/agentctl/cli" "github.com/ligato/vpp-agent/pkg/debug" -) -var ( - // RootName defines default name used for the root command. - RootName = "agentctl" + "github.com/ligato/vpp-agent/cmd/agentctl/cli" ) -// NewAgentCli creates new AgentCli with opts and configures log output to error stream. -func NewAgentCli(opts ...cli.AgentCliOption) *cli.AgentCli { - agentCli, err := cli.NewAgentCli(opts...) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - logrus.SetOutput(agentCli.Err()) - logging.DefaultLogger.SetOutput(agentCli.Err()) - return agentCli -} - -// NewRootCommand is helper for default initialization process for root command. -// Returs cobra command which is ready to be executed. -func NewRootCommand(agentCli *cli.AgentCli) (*cobra.Command, error) { - root := NewRoot(agentCli) - cmd, err := root.SetupCommand() - if err != nil { - return nil, err - } - if err := root.Initialize(); err != nil { - return nil, err - } - return cmd, nil -} - -// SetupCommand handles global flags and Initialize should be -// called before executing returned cobra command. -func (root *Root) SetupCommand() (*cobra.Command, error) { - cmd, args, err := root.HandleGlobalFlags() - if err != nil { - return nil, err - } - if debug.IsEnabledFor("flags") { - cmd.DebugFlags() - } - cmd.SetArgs(args) - return cmd, nil -} - -// NewRoot returns new Root using RootName for name. -func NewRoot(agentCli *cli.AgentCli) *Root { - return NewRootNamed(RootName, agentCli) -} - // NewRootNamed returns new Root named with name. func NewRootNamed(name string, agentCli *cli.AgentCli) *Root { var ( @@ -124,6 +73,20 @@ func NewRootNamed(name string, agentCli *cli.AgentCli) *Root { return newRoot(cmd, agentCli, opts, flags) } +// PrepareCommand handles global flags and Initialize should be +// called before executing returned cobra command. +func (root *Root) PrepareCommand() (*cobra.Command, error) { + cmd, args, err := root.HandleGlobalFlags() + if err != nil { + return nil, err + } + if debug.IsEnabledFor("flags") { + cmd.DebugFlags() + } + cmd.SetArgs(args) + return cmd, nil +} + // Root encapsulates a top-level cobra command (either agentctl or custom one). type Root struct { cmd *cobra.Command @@ -202,20 +165,6 @@ func SetupRootCommand(rootCmd *cobra.Command) (*cli.ClientOptions, *pflag.FlagSe return opts, flags, helpCommand } -// AddBaseCommands adds all base commands to cmd. -func AddBaseCommands(cmd *cobra.Command, cli cli.Cli) { - cmd.AddCommand( - NewModelCommand(cli), - NewLogCommand(cli), - NewImportCommand(cli), - NewVppCommand(cli), - NewDumpCommand(cli), - NewKvdbCommand(cli), - NewGenerateCommand(cli), - NewStatusCommand(cli), - ) -} - func setFlagGlobal(flags *pflag.FlagSet, name string) { _ = flags.SetAnnotation(name, "global", []string{"yes"}) } diff --git a/cmd/agentctl/commands/status.go b/cmd/agentctl/commands/status.go index d7c70e5fcb..a7f0378742 100644 --- a/cmd/agentctl/commands/status.go +++ b/cmd/agentctl/commands/status.go @@ -71,7 +71,7 @@ func runStatus(cli agentcli.Cli, opts StatusOptions) error { } } - status, err := cli.Client().SchedulerStatus(ctx, types.SchedulerStatusOptions{ + status, err := cli.Client().SchedulerValues(ctx, types.SchedulerValuesOptions{ KeyPrefix: modelKeyPrefix, }) if err != nil { diff --git a/cmd/agentctl/commands/values.go b/cmd/agentctl/commands/values.go new file mode 100644 index 0000000000..fecae5e22a --- /dev/null +++ b/cmd/agentctl/commands/values.go @@ -0,0 +1,137 @@ +// Copyright (c) 2019 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "bytes" + "context" + "fmt" + "io" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" + + "github.com/ligato/vpp-agent/api/types" + agentcli "github.com/ligato/vpp-agent/cmd/agentctl/cli" + "github.com/ligato/vpp-agent/pkg/models" + "github.com/ligato/vpp-agent/plugins/kvscheduler/api" +) + +func NewValuesCommand(cli agentcli.Cli) *cobra.Command { + var opts ValuesOptions + + cmd := &cobra.Command{ + Use: "values [model]", + Short: "Retrieve values from scheduler", + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + opts.Models = args + return runValues(cli, opts) + }, + } + return cmd +} + +type ValuesOptions struct { + Models []string +} + +func runValues(cli agentcli.Cli, opts ValuesOptions) error { + var model string + if len(opts.Models) > 0 { + model = opts.Models[0] + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + allModels, err := cli.Client().ModelList(ctx, types.ModelListOptions{}) + if err != nil { + return err + } + + var modelKeyPrefix string + for _, m := range allModels { + if (m.Alias != "" && model == m.Alias) || model == m.Name { + modelKeyPrefix = m.KeyPrefix + break + } + } + + status, err := cli.Client().SchedulerValues(ctx, types.SchedulerValuesOptions{ + KeyPrefix: modelKeyPrefix, + }) + if err != nil { + return err + } + + if err := printValuesTable(cli.Out(), status); err != nil { + return err + } + + return nil +} + +// printValuesTable prints values data using table format +func printValuesTable(out io.Writer, status []*api.BaseValueStatus) error { + var buf bytes.Buffer + + w := tabwriter.NewWriter(&buf, 10, 0, 3, ' ', 0) + fmt.Fprintf(w, "MODEL\tNAME\tSTATE\tDETAILS\tLAST OP\tERROR\t\n") + + var printVal = func(val *api.ValueStatus) { + var ( + model string + name string + ) + + m, err := models.GetModelForKey(val.Key) + if err != nil { + name = val.Key + } else { + model = fmt.Sprintf("%s.%s", m.Module, m.Type) + name = m.StripKeyPrefix(val.Key) + } + + var lastOp string + if val.LastOperation != api.TxnOperation_UNDEFINED { + lastOp = val.LastOperation.String() + } + state := val.State.String() + if val.State == api.ValueState_OBTAINED { + state = strings.ToLower(state) + } + + var details string + if len(val.Details) > 0 { + details = strings.Join(val.Details, ", ") + } + + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n", model, name, state, details, lastOp, val.Error) + } + + for _, d := range status { + printVal(d.Value) + for _, v := range d.DerivedValues { + printVal(v) + } + } + if err := w.Flush(); err != nil { + return err + } + _, err := buf.WriteTo(out) + return err +} diff --git a/cmd/agentctl/commands/vpp.go b/cmd/agentctl/commands/vpp.go index 0dcf8cfec1..f6aea0e0e9 100644 --- a/cmd/agentctl/commands/vpp.go +++ b/cmd/agentctl/commands/vpp.go @@ -91,11 +91,17 @@ func runVppInfo(cli agentcli.Cli) error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - reply, err := cli.Client().VppRunCli(ctx, "show version verbose") + version, err := cli.Client().VppRunCli(ctx, "show version verbose") if err != nil { return err } + fmt.Fprintf(cli.Out(), "VERSION:\n%s\n", version) + + config, err := cli.Client().VppRunCli(ctx, "show version cmdline") + if err != nil { + return err + } + fmt.Fprintf(cli.Out(), "CONFIG:\n%s\n", config) - fmt.Fprintf(cli.Out(), "%s", reply) return nil } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index d7b7385d92..53865f9354 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -16,7 +16,6 @@ package e2e import ( "bytes" - "encoding/json" "flag" "fmt" "log" @@ -29,13 +28,15 @@ import ( "testing" "time" + "encoding/json" + docker "github.com/fsouza/go-dockerclient" "github.com/gogo/protobuf/proto" + "github.com/ligato/cn-infra/health/probe" "github.com/mitchellh/go-ps" . "github.com/onsi/gomega" "google.golang.org/grpc" - "github.com/ligato/cn-infra/health/probe" "github.com/ligato/cn-infra/health/statuscheck/model/status" "github.com/ligato/vpp-agent/api/genericmanager" "github.com/ligato/vpp-agent/client"