Skip to content

Commit

Permalink
Add info subcommand to CLI
Browse files Browse the repository at this point in the history
Adds `info` subcommand, which contains:
- `cache`: worker capacity information, calls the existing `bin/alluxio fsadmin report capacity` command
- `collect`: collects cluster information into a single tarball, calls the existing `bin/alluxio collectInfo` command
- `master`: master quorum information, calls the existing `bin/alluxio fs masterInfo` command
- `report`: cluster information, calls the existing `bin/alluxio fsadmin report` command, excluding the `capacity` category

Alluxio#17522
			pr-link: Alluxio#17566
			change-id: cid-2483fdb693f1572b0bf9c798503ea9cfd8e52b1d
  • Loading branch information
Xenorith authored and twalluxio committed Aug 14, 2023
1 parent 61c9bb2 commit f3d5ce5
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 8 deletions.
19 changes: 18 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,29 @@ bin/cli.sh item mark --unset name
where it is expected that either `--set` or `--unset` are specified.
This is preferred over the alternative of two separate commands with `setMark` and `unsetMark` as the operations.

## User input

### Flags and arguments
After selecting the desired command, additional user input can be parsed, as a mix of arguments and/or flags:
- Arguments: `bin/cli.sh command arg1 arg2 ...`
- Flags: `bin/cli.sh command --flag1 --flag2 val2 ...`

Flags are strictly preferred over arguments.
- The flag name conveys context; an argument does not
- Arguments must be ordered; flags can be declared arbitrarily
- Flags can be designated as required to ensure user input.
- Repeated flags can be defined to capture an ordered list of inputs, ex. `--target target1 --target target2`

### Input validation

User inputs should be validated by the CLI command as much as possible as opposed to the resulting invocation.

## Output conventions and java invocation

A majority of commands result in invoking a java class with arguments to execute the expected operation and possibly return some output.
The output returned from the java invocation should tend towards being plain or machine parseable, such as a JSON formatted string,
rather than terminal friendly or human readable format.
When appropriate, the CLI command will default to formatting this output to be terminal friendly, with an option to output in a machine parseable format such as JSON.
When appropriate, the CLI command will default to formatting this output to be terminal friendly, with an option to output in a machine parseable format.

## References

Expand Down
5 changes: 3 additions & 2 deletions cli/src/alluxio.org/cli/cmd/conf/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ func (c *LogCommand) ToCommand() *cobra.Command {
return c.Run(args)
},
})
cmd.Flags().StringVar(&c.LogName, "name", "", "Logger name (ex. alluxio.master.file.DefaultFileSystemMaster)")
if err := cmd.MarkFlagRequired("name"); err != nil {
const name = "name"
cmd.Flags().StringVar(&c.LogName, name, "", "Logger name (ex. alluxio.master.file.DefaultFileSystemMaster)")
if err := cmd.MarkFlagRequired(name); err != nil {
panic(err)
}
cmd.Flags().StringVar(&c.Level, "level", "", "If specified, sets the specified logger at the given level")
Expand Down
70 changes: 70 additions & 0 deletions cli/src/alluxio.org/cli/cmd/info/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/

package info

import (
"strings"

"github.com/spf13/cobra"

"alluxio.org/cli/env"
)

var Cache = &CacheCommand{
BaseJavaCommand: &env.BaseJavaCommand{
CommandName: "cache",
JavaClassName: "alluxio.cli.fsadmin.FileSystemAdminShell",
Parameters: []string{"report", "capacity"},
},
}

type CacheCommand struct {
*env.BaseJavaCommand

liveWorkers bool
lostWorkers bool
workersList []string // list of worker hostnames or ip addresses
}

func (c *CacheCommand) Base() *env.BaseJavaCommand {
return c.BaseJavaCommand
}

func (c *CacheCommand) ToCommand() *cobra.Command {
cmd := c.Base().InitRunJavaClassCmd(&cobra.Command{
Use: Cache.CommandName,
Short: "Reports worker capacity information",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(nil)
},
})
const live, lost, worker = "live", "lost", "worker"
cmd.Flags().BoolVar(&c.liveWorkers, live, false, "Only show live workers for capacity report")
cmd.Flags().BoolVar(&c.lostWorkers, lost, false, "Only show lost workers for capacity report")
cmd.Flags().StringSliceVar(&c.workersList, worker, nil, "Only show specified workers for capacity report, labeled by hostname or IP address")
cmd.MarkFlagsMutuallyExclusive(live, lost, worker)
return cmd
}

func (c *CacheCommand) Run(_ []string) error {
// TODO: output all in a serializable format and filter/trim as specified by flags
var args []string
if c.liveWorkers {
args = append(args, "-live")
} else if c.lostWorkers {
args = append(args, "-lost")
} else if len(c.workersList) > 0 {
args = append(args, "-workers", strings.Join(c.workersList, ","))
}
return c.Base().Run(args)
}
153 changes: 153 additions & 0 deletions cli/src/alluxio.org/cli/cmd/info/collect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/

package info

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/palantir/stacktrace"
"github.com/spf13/cobra"

"alluxio.org/cli/env"
)

var Collect = &CollectCommand{
BaseJavaCommand: &env.BaseJavaCommand{
CommandName: "collect",
JavaClassName: "alluxio.cli.bundler.CollectInfo",
},
}

type CollectCommand struct {
*env.BaseJavaCommand

additionalLogs []string
endTime string
excludeLogs []string
excludeWorkerMetrics bool
includeLogs []string
local bool
maxThreads int
outputPath string
startTime string
}

func (c *CollectCommand) Base() *env.BaseJavaCommand {
return c.BaseJavaCommand
}

const dateFormat = "2006-01-02T15:04:05"

func (c *CollectCommand) ToCommand() *cobra.Command {
cmd := c.Base().InitRunJavaClassCmd(&cobra.Command{
Use: fmt.Sprintf("%v [command]", Collect.CommandName),
Short: "Collects information such as logs, config, metrics, and more from the running Alluxio cluster and bundle into a single tarball",
Long: `Collects information such as logs, config, metrics, and more from the running Alluxio cluster and bundle into a single tarball
[command] must be one of the following values:
all runs all the commands below
cluster: runs a set of Alluxio commands to collect information about the Alluxio cluster
conf: collects the configuration files under ${ALLUXIO_HOME}/config/
env: runs a set of linux commands to collect information about the cluster
jvm: collects jstack from the JVMs
log: collects the log files under ${ALLUXIO_HOME}/logs/
metrics: collects Alluxio system metrics
WARNING: This command MAY bundle credentials. To understand the risks refer to the docs here.
https://docs.alluxio.io/os/user/edge/en/operation/Troubleshooting.html#collect-alluxio-cluster-information
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(args)
},
})
cmd.Flags().StringSliceVar(&c.additionalLogs, "additional-logs", nil, "Additional file name prefixes from ${ALLUXIO_HOME}/logs to include in the tarball, inclusive of the default log files")
cmd.Flags().StringVar(&c.endTime, "end-time", "", "Logs that do not contain entries before this time will be ignored, format must be like "+dateFormat)
cmd.Flags().StringSliceVar(&c.excludeLogs, "exclude-logs", nil, "File name prefixes from ${ALLUXIO_HOME}/logs to exclude; this is evaluated after adding files from --additional-logs")
cmd.Flags().BoolVar(&c.excludeWorkerMetrics, "exclude-worker-metrics", false, "True to skip worker metrics collection")
cmd.Flags().StringSliceVar(&c.includeLogs, "include-logs", nil, "File name prefixes from ${ALLUXIO_HOME}/logs to include in the tarball, ignoring the default log files; cannot be used with --exclude-logs or --additional-logs")
cmd.Flags().BoolVar(&c.local, "local", false, "True to only collect information from the local machine")
cmd.Flags().IntVar(&c.maxThreads, "max-threads", 1, "Parallelism of the command; use a smaller value to limit network I/O when transferring tarballs")
const outputPath = "output-path"
cmd.Flags().StringVar(&c.outputPath, outputPath, "", "Output directory to write collect info tarball to")
if err := cmd.MarkFlagRequired(outputPath); err != nil {
panic(err)
}
cmd.Flags().StringVar(&c.startTime, "start-time", "", "Logs that do not contain entries after this time will be ignored, format must be like "+dateFormat)
return cmd
}

func (c *CollectCommand) Run(args []string) error {
// TODO: use flags instead of arguments to parse user input
commands := map[string]string{
"all": "all",
"cluster": "collectAlluxioInfo",
"conf": "collectConfig",
"env": "collectEnv",
"jvm": "collectJvmInfo",
"log": "collectLog",
"metrics": "collectMetrics",
}
commandArg, ok := commands[args[0]]
if !ok {
var cmds []string
for c := range commands {
cmds = append(cmds, c)
}
return stacktrace.NewError("first argument must be one of %v", strings.Join(cmds, ", "))
}

var javaArgs []string
if c.additionalLogs != nil {
if c.includeLogs != nil {
return stacktrace.NewError("cannot set both --include-logs and --additional-logs")
}
javaArgs = append(javaArgs, "--additional-logs", strings.Join(c.additionalLogs, ","))
}
if c.endTime != "" {
if _, err := time.Parse(dateFormat, c.endTime); err != nil {
return stacktrace.Propagate(err, "could not parse end time %v", c.endTime)
}
javaArgs = append(javaArgs, "--end-time", c.endTime)
}
if c.excludeLogs != nil {
if c.includeLogs != nil {
return stacktrace.NewError("cannot set both --include-logs and --exclude-logs")
}
javaArgs = append(javaArgs, "--exclude-logs", strings.Join(c.excludeLogs, ","))
}
if c.excludeWorkerMetrics {
javaArgs = append(javaArgs, "--exclude-worker-metrics")
}
if c.includeLogs != nil {
// already checked exclusivity with --additional-logs and --exclude-logs
javaArgs = append(javaArgs, "--include-logs", strings.Join(c.includeLogs, ","))
}
if c.local {
javaArgs = append(javaArgs, "--local")
}
if c.maxThreads > 1 {
javaArgs = append(javaArgs, "--max-threads", strconv.Itoa(c.maxThreads))
}
if c.startTime != "" {
if _, err := time.Parse(dateFormat, c.startTime); err != nil {
return stacktrace.Propagate(err, "could not parse start time %v", c.startTime)
}
javaArgs = append(javaArgs, "--start-time", c.startTime)
}

javaArgs = append(javaArgs, commandArg, c.outputPath)

return c.Base().Run(javaArgs)
}
27 changes: 27 additions & 0 deletions cli/src/alluxio.org/cli/cmd/info/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/

package info

import (
"alluxio.org/cli/env"
)

var Service = &env.Service{
Name: "info",
Description: "Retrieve and/or display info about the running Alluxio cluster",
Commands: []env.Command{
Cache,
Collect,
Master,
Report,
},
}
50 changes: 50 additions & 0 deletions cli/src/alluxio.org/cli/cmd/info/master.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/

package info

import (
"github.com/spf13/cobra"

"alluxio.org/cli/env"
)

var Master = &MasterCommand{
BaseJavaCommand: &env.BaseJavaCommand{
CommandName: "master",
JavaClassName: "alluxio.cli.fs.FileSystemShell",
Parameters: []string{"masterInfo"},
},
}

type MasterCommand struct {
*env.BaseJavaCommand
}

func (c *MasterCommand) Base() *env.BaseJavaCommand {
return c.BaseJavaCommand
}

func (c *MasterCommand) ToCommand() *cobra.Command {
cmd := c.Base().InitRunJavaClassCmd(&cobra.Command{
Use: Master.CommandName,
Short: "Prints information regarding master fault tolerance such as leader address and list of master addresses",
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(nil)
},
})
return cmd
}

func (c *MasterCommand) Run(_ []string) error {
// TODO: output in a serializable format
return c.Base().Run(nil)
}
Loading

0 comments on commit f3d5ce5

Please sign in to comment.