diff --git a/docs/reference/66_reporters.adoc b/docs/reference/66_reporters.adoc index 5ee65318..65646c4d 100644 --- a/docs/reference/66_reporters.adoc +++ b/docs/reference/66_reporters.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2021, 2023 Oracle and/or its affiliates. + Copyright (c) 2021, 2024 Oracle and/or its affiliates. Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. @@ -18,6 +18,7 @@ There are various commands that allow you to work with and manage Reporters. * <> - starts a reporter on a specific node * <> - stops a reporter on a specific node * <> - sets a reporter attribute for one or more members +* <> - runs a report on a specific node and returns the report output in JSON [#get-reporters] ==== Get Reporters @@ -168,9 +169,34 @@ NODE ID STATE CONFIG FILE OUTPUT PATH BATCH# LAST REPORT LA 2 Stopped reports/report-group.xml /tmp 0 0ms 0.0000ms 60 false ---- +[#run-report] +==== Run + +include::../../build/_output/docs-gen/run_report.adoc[tag=text] + +NOTE: The otuput will always be JSON. You can use `-o jsonpath=...` to use jsonpath expression or pipe through to a utility such as `jq`. + +This REST endpoint that this command uses is only available in the most recent Coherence releases. +You will receive a HTTP 400 error if it is not supported in your Coherence version. + +[source,bash] +---- +cohctl run report report-node -c local -n 1 +---- +Output: +[source,bash] +---- +{"items":[{"RefreshTime":"Tue Oct 15 09:07:55 AWST 2024","ReportTime":"Tue Oct 15 09:07:55 AWST 2024", +... +"RoleName":"CoherenceServer","Addres,"BatchCounter":"0","rowID":3}]} +---- + +NOTE: The output above is truncated for readability. + === See Also * <> +* <> diff --git a/pkg/cmd/cache.go b/pkg/cmd/cache.go index 45be7b54..35ef46c8 100644 --- a/pkg/cmd/cache.go +++ b/pkg/cmd/cache.go @@ -42,6 +42,8 @@ const ( back = "back" all = "all" partitionDisplayType = "partition" + access = "access" + storage = "storage" ) // getCachesCmd represents the get caches command. @@ -452,7 +454,7 @@ You can specify '-o wide' to display addition information.`, return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return getCacheDetails(cmd, args, "access") + return getCacheDetails(cmd, args, access) }, } @@ -470,7 +472,7 @@ You can specify '-o wide' to display addition information.`, return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return getCacheDetails(cmd, args, "storage") + return getCacheDetails(cmd, args, storage) }, } @@ -660,11 +662,11 @@ func getCacheDetails(cmd *cobra.Command, args []string, displayType string) erro cmd.Printf("Cache: %s\n\n", args[0]) } - if displayType == "access" { + if displayType == access { cmd.Println(FormatCacheDetailsSizeAndAccess(cacheDetails.Details)) } else if displayType == "index" { cmd.Println(FormatCacheIndexDetails(cacheDetails.Details)) - } else if displayType == "storage" { + } else if displayType == storage { cmd.Println(FormatCacheDetailsStorage(cacheDetails.Details)) } else if displayType == partitionDisplayType { cmd.Printf("Cache: %s\n", args[0]) diff --git a/pkg/cmd/cluster.go b/pkg/cmd/cluster.go index ea9cacb3..30ffbfd0 100644 --- a/pkg/cmd/cluster.go +++ b/pkg/cmd/cluster.go @@ -735,6 +735,7 @@ var ( machineParam string rackParam string siteParam string + roleParam string skipMavenDepsParam bool backupLogFilesParam bool validPersistenceModes = []string{"on-demand", "active", "active-backup", "active-async"} @@ -1456,6 +1457,7 @@ func init() { createClusterCmd.Flags().StringVarP(&machineParam, machineArg, "", "", machineMessage) createClusterCmd.Flags().StringVarP(&rackParam, rackArg, "", "", rackMessage) createClusterCmd.Flags().StringVarP(&siteParam, siteArg, "", "", siteMessage) + createClusterCmd.Flags().StringVarP(&roleParam, roleArg, "", "", roleMessage) stopClusterCmd.Flags().BoolVarP(&automaticallyConfirm, "yes", "y", false, confirmOptionMessage) @@ -1474,6 +1476,7 @@ func init() { startClusterCmd.Flags().StringVarP(&machineParam, machineArg, "", "", machineMessage) startClusterCmd.Flags().StringVarP(&rackParam, rackArg, "", "", rackMessage) startClusterCmd.Flags().StringVarP(&siteParam, siteArg, "", "", siteMessage) + startClusterCmd.Flags().StringVarP(&roleParam, roleArg, "", "", roleMessage) startConsoleCmd.Flags().StringVarP(&heapMemoryParam, heapMemoryArg, "M", defaultHeap, heapMemoryMessage) startConsoleCmd.Flags().Int32VarP(&logLevelParam, logLevelArg, "l", 5, logLevelMessage) @@ -1505,6 +1508,7 @@ func init() { scaleClusterCmd.Flags().StringVarP(&machineParam, machineArg, "", "", machineMessage) scaleClusterCmd.Flags().StringVarP(&rackParam, rackArg, "", "", rackMessage) scaleClusterCmd.Flags().StringVarP(&siteParam, siteArg, "", "", siteMessage) + scaleClusterCmd.Flags().StringVarP(&roleParam, roleArg, "", "", roleMessage) } // sanitizeConnectionName sanitizes a cluster connection diff --git a/pkg/cmd/cluster_utils.go b/pkg/cmd/cluster_utils.go index 5a7531a0..fac3d328 100644 --- a/pkg/cmd/cluster_utils.go +++ b/pkg/cmd/cluster_utils.go @@ -420,6 +420,10 @@ func getCacheServerArgs(connection ClusterConnection, member string, httpPort in baseArgs = append(baseArgs, fmt.Sprintf("-Dcoherence.site=%s", siteParam)) } + if roleParam != "" { + baseArgs = append(baseArgs, fmt.Sprintf("-Dcoherence.role=%s", roleParam)) + } + // if default heap is overridden, then use this if heapMemoryParam != defaultHeap { heap = heapMemoryParam @@ -661,7 +665,7 @@ func runCommandBase(command, logFileName string, arguments []string) (string, er Logger.Info("Run command", fields...) } - start := time.Now() + startTime := time.Now() process := exec.Command(command, arguments...) if len(logFileName) > 0 { // a log file was supplied, so we are assuming this command will be async and @@ -684,7 +688,7 @@ func runCommandBase(command, logFileName string, arguments []string) (string, er if Config.Debug { fields := []zapcore.Field{ - zap.String("time", fmt.Sprintf("%v", time.Since(start))), + zap.String("time", fmt.Sprintf("%v", time.Since(startTime))), } Logger.Info("Duration", fields...) } diff --git a/pkg/cmd/formatting.go b/pkg/cmd/formatting.go index fd34e37c..8b7d5995 100644 --- a/pkg/cmd/formatting.go +++ b/pkg/cmd/formatting.go @@ -1017,6 +1017,9 @@ func FormatCacheDetailsStorage(cacheDetails []config.CacheDetail) string { } for _, value := range cacheDetails { + if value.Tier != "back" { + continue + } var nodeID, _ = strconv.Atoi(value.NodeID) table.AddRow(formatSmallInteger(int32(nodeID)), value.Tier, diff --git a/pkg/cmd/monitor_cluster.go b/pkg/cmd/monitor_cluster.go index 7f7136cd..bd12bf29 100644 --- a/pkg/cmd/monitor_cluster.go +++ b/pkg/cmd/monitor_cluster.go @@ -959,11 +959,11 @@ func getCacheContent(dataFetcher fetcher.Fetcher, displayType string) ([]string, return emptyStringArray, err } - if displayType == "access" { + if displayType == access { sb.WriteString(FormatCacheDetailsSizeAndAccess(cacheDetails.Details)) } else if displayType == "index" { sb.WriteString(FormatCacheIndexDetails(cacheDetails.Details)) - } else if displayType == "storage" { + } else if displayType == storage { sb.WriteString(FormatCacheDetailsStorage(cacheDetails.Details)) } else if displayType == partitionDisplayType { sb.WriteString(FormatCachePartitions(cachePartitionDetails.Details, cacheSummary)) diff --git a/pkg/cmd/reporter.go b/pkg/cmd/reporter.go index feba1ac7..90bfbf0c 100644 --- a/pkg/cmd/reporter.go +++ b/pkg/cmd/reporter.go @@ -33,6 +33,7 @@ var ( validReporterAttributes = []string{reporterConfigFile, reporterCurrentBatch, reporterIntervalSeconds, reporterOutputPath} reporterAttributeName string reporterAttributeValue string + reporterNodID int ) // getReportersCmd represents the get reporters command. @@ -155,6 +156,73 @@ var describeReporterCmd = &cobra.Command{ }, } +// runReportCmd represents the run report command. +var runReportCmd = &cobra.Command{ + Use: "report report-name", + Short: "run a report and return the output", + Long: `The 'run report' command runs a report on a specific node and returns the report output in JSON. +The report name should not include the .xml extension and will have the 'report' prefix added. E.g. +'report-node' will expand to 'reports/report-node.xml'. A HTTP 400 will be returned if the report name is not valid.`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + displayErrorAndExit(cmd, "you must provide a report name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + var ( + jsonData []byte + err error + dataFetcher fetcher.Fetcher + found = false + ) + + // retrieve the current context or the value from "-c" + _, dataFetcher, err = GetConnectionAndDataFetcher() + if err != nil { + return err + } + + // validate the nodeID + nodeIDArray, err := GetClusterNodeIDs(dataFetcher) + if err != nil { + return err + } + for _, v := range nodeIDArray { + i, _ := strconv.Atoi(v) + if i == reporterNodID { + found = true + } + } + if !found { + return fmt.Errorf("unable to find node id %v", reporterNodID) + } + + jsonData, err = dataFetcher.RunReportJSON(args[0], reporterNodID) + if err != nil { + return err + } + + // output format cannot be table + if OutputFormat == constants.TABLE { + OutputFormat = constants.JSON + } + + if strings.Contains(OutputFormat, constants.JSONPATH) { + jsonPathResult, err := utils.GetJSONPathResults(jsonData, OutputFormat) + if err != nil { + return err + } + cmd.Println(jsonPathResult) + return nil + } else if OutputFormat == constants.JSON { + cmd.Println(string(jsonData)) + } + + return nil + }, +} + // startReporterCmd represents the start reporter command. var startReporterCmd = &cobra.Command{ Use: reporterUse, @@ -309,4 +377,7 @@ func init() { _ = setReporterCmd.MarkFlagRequired("attribute") setReporterCmd.Flags().StringVarP(&reporterAttributeValue, "value", "v", "", "attribute value to set") _ = setReporterCmd.MarkFlagRequired("value") + + runReportCmd.Flags().IntVarP(&reporterNodID, "node", "n", 0, "node to run report on") + _ = runReportCmd.MarkFlagRequired("node") } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 93e3f946..5f557294 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -91,6 +91,7 @@ const ( machineMessage = "the machine name to use" rackMessage = "the rack name to use" siteMessage = "the site name to use" + roleMessage = "the role name to use" cacheConfigMessage = "cache configuration file" operationalConfigMessage = "override override file" cacheConfigArg = "cache-config" @@ -103,6 +104,7 @@ const ( machineArg = "machine" rackArg = "rack" siteArg = "site" + roleArg = "role" logLevelMessage = "coherence log level" profileMessage = "profile to add to cluster startup command line" backupLogFilesMessage = "backup old cache server log files" @@ -570,6 +572,10 @@ func Initialize(command *cobra.Command) *cobra.Command { setCmd.AddCommand(setFederationCmd) setCmd.AddCommand(setColorCmd) + // run command + command.AddCommand(runCmd) + runCmd.AddCommand(runReportCmd) + // clear command.AddCommand(clearCmd) clearCmd.AddCommand(clearContextCmd) diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go new file mode 100644 index 00000000..b218657b --- /dev/null +++ b/pkg/cmd/run.go @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package cmd + +import ( + "github.com/spf13/cobra" +) + +// runCmd represents the run. +var runCmd = &cobra.Command{ + Use: "run", + Short: "run a report", + Long: `The 'run' command runs reports`, +} diff --git a/pkg/fetcher/fetcher.go b/pkg/fetcher/fetcher.go index ca696f2b..06ad02b2 100644 --- a/pkg/fetcher/fetcher.go +++ b/pkg/fetcher/fetcher.go @@ -223,6 +223,9 @@ type Fetcher interface { // GetReporterJSON returns reporter for a node in raw json. GetReporterJSON(nodeID string) ([]byte, error) + // RunReportJSON runs a specified report. + RunReportJSON(report string, nodeID int) ([]byte, error) + // StartReporter starts the reporter on a member. StartReporter(nodeID string) error diff --git a/pkg/fetcher/http_fetcher.go b/pkg/fetcher/http_fetcher.go index 3d85068a..5b569857 100644 --- a/pkg/fetcher/http_fetcher.go +++ b/pkg/fetcher/http_fetcher.go @@ -764,6 +764,15 @@ func (h HTTPFetcher) GetReporterJSON(nodeID string) ([]byte, error) { return result, nil } +// RunReportJSON runs a specified report. +func (h HTTPFetcher) RunReportJSON(report string, nodeID int) ([]byte, error) { + result, err := httpGetRequest(h, fmt.Sprintf("%s%d/runReport/%s%s", reportersPath, nodeID, report, links)) + if err != nil { + return constants.EmptyByte, utils.GetError(fmt.Sprintf("cannot run report %v on node %v", report, nodeID), err) + } + return result, nil +} + // StartReporter starts the reporter on a member. func (h HTTPFetcher) StartReporter(nodeID string) error { _, err := issueReporterCommand(h, nodeID, "start") diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index c074f16f..bc083269 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -162,9 +162,7 @@ func IsValidInt(value string) bool { // SanitizeSnapshotName sanitizes a snapshot name by replacing any unwanted characters with '-'. func SanitizeSnapshotName(snapshotName string) string { - var ( - sb = strings.Builder{} - ) + var sb = strings.Builder{} for _, c := range []byte(snapshotName) { r := rune(c) diff --git a/scripts/generate-doc-snippets.sh b/scripts/generate-doc-snippets.sh index e9ae6716..6c786a19 100755 --- a/scripts/generate-doc-snippets.sh +++ b/scripts/generate-doc-snippets.sh @@ -170,6 +170,7 @@ create_doc $DOCS_DIR/describe_reporter "${COHCTL} describe reporter --help" create_doc $DOCS_DIR/start_reporter "${COHCTL} start reporter --help" create_doc $DOCS_DIR/stop_reporter "${COHCTL} stop reporter --help" create_doc $DOCS_DIR/set_reporter "${COHCTL} set reporter --help" +create_doc $DOCS_DIR/run_report "${COHCTL} run report --help" # JFRs create_doc $DOCS_DIR/get_jfrs "${COHCTL} get jfrs --help"