Skip to content

Commit 8aa4d76

Browse files
committed
feat: add support for displaying power status
Signed-off-by: Chris Harris <cjh@lbl.gov>
1 parent 1219bee commit 8aa4d76

File tree

7 files changed

+357
-2
lines changed

7 files changed

+357
-2
lines changed

cmd/pcs-status-list.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// This source code is licensed under the license found in the LICENSE file at
2+
// the root directory of this source tree.
3+
package cmd
4+
5+
import (
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"os"
10+
"strings"
11+
12+
"github.com/spf13/cobra"
13+
14+
"github.com/OpenCHAMI/ochami/internal/log"
15+
"github.com/OpenCHAMI/ochami/pkg/client"
16+
"github.com/OpenCHAMI/ochami/pkg/format"
17+
)
18+
19+
type PowerFilter string
20+
21+
var powerFilter PowerFilter = ""
22+
23+
const (
24+
powerOn PowerFilter = "on"
25+
powerOff PowerFilter = "off"
26+
powerUndefined PowerFilter = "undefined"
27+
)
28+
29+
func (l *PowerFilter) String() string {
30+
return string(*l)
31+
}
32+
33+
func (l *PowerFilter) Set(value string) error {
34+
switch strings.ToLower(value) {
35+
case "on", "off", "undefined":
36+
*l = PowerFilter(strings.ToLower(value))
37+
return nil
38+
default:
39+
return fmt.Errorf("invalid power filter: %s (must be on, off, or undefined)", value)
40+
}
41+
}
42+
43+
func (l PowerFilter) Type() string {
44+
return "PowerFilter"
45+
}
46+
47+
var (
48+
powerFilterHelp = map[string]string{
49+
string(powerOn): "Include components that are powered on",
50+
string(powerOff): "Include components that are powered off",
51+
string(powerUndefined): "Include components with undefined power state",
52+
}
53+
)
54+
55+
func pcsStatusListPowerFilterCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
56+
var helpSlice []string
57+
for k, v := range powerFilterHelp {
58+
helpSlice = append(helpSlice, fmt.Sprintf("%s\t%s", k, v))
59+
}
60+
return helpSlice, cobra.ShellCompDirectiveNoFileComp
61+
}
62+
63+
type MgmtFilter string
64+
65+
var mgmtFilter MgmtFilter = ""
66+
67+
func (l *MgmtFilter) String() string {
68+
return string(*l)
69+
}
70+
71+
func (l *MgmtFilter) Set(value string) error {
72+
switch strings.ToLower(value) {
73+
case "available", "unavailable":
74+
*l = MgmtFilter(strings.ToLower(value))
75+
return nil
76+
default:
77+
return fmt.Errorf("invalid management filter: %s (must be available or unavailable)", value)
78+
}
79+
}
80+
81+
func (l MgmtFilter) Type() string {
82+
return "MgmtFilter"
83+
}
84+
85+
var (
86+
mgmtFilterHelp = map[string]string{
87+
"available": "Include components that are available",
88+
"unavailable": "Include components that are unavailable",
89+
}
90+
)
91+
92+
func pcsStatusListMgmtFilterCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
93+
var helpSlice []string
94+
for k, v := range mgmtFilterHelp {
95+
helpSlice = append(helpSlice, fmt.Sprintf("%s\t%s", k, v))
96+
}
97+
return helpSlice, cobra.ShellCompDirectiveNoFileComp
98+
}
99+
100+
// pcsStatusListCmd represents the "pcs status list" command
101+
var pcsStatusListCmd = &cobra.Command{
102+
Use: "list",
103+
Args: cobra.NoArgs,
104+
Short: "List active PCS transitions",
105+
Long: `List active PCS transitions.
106+
107+
See ochami-pcs(1) for more details.`,
108+
Example: ` # List status
109+
ochami pcs status list`,
110+
Run: func(cmd *cobra.Command, args []string) {
111+
// Create client to use for requests
112+
pcsClient := pcsGetClient(cmd)
113+
114+
// Handle token for this command
115+
handleToken(cmd)
116+
117+
// Get status
118+
statusHttpEnv, err := pcsClient.GetStatus(xnames, string(powerFilter), string(mgmtFilter), token)
119+
if err != nil {
120+
if errors.Is(err, client.UnsuccessfulHTTPError) {
121+
log.Logger.Error().Err(err).Msg("PCS status request yielded unsuccessful HTTP response")
122+
} else {
123+
log.Logger.Error().Err(err).Msg("failed to list PCS transitions")
124+
}
125+
logHelpError(cmd)
126+
os.Exit(1)
127+
}
128+
129+
var output interface{}
130+
err = json.Unmarshal(statusHttpEnv.Body, &output)
131+
if err != nil {
132+
log.Logger.Error().Err(err).Msg("failed to unmarshal status response")
133+
logHelpError(cmd)
134+
os.Exit(1)
135+
}
136+
137+
// Print output
138+
if outBytes, err := format.MarshalData(output, formatOutput); err != nil {
139+
log.Logger.Error().Err(err).Msg("failed to format output")
140+
logHelpError(cmd)
141+
os.Exit(1)
142+
} else {
143+
fmt.Println(string(outBytes))
144+
}
145+
},
146+
}
147+
148+
func init() {
149+
pcsStatusListCmd.Flags().StringSliceVarP(&xnames, "xname", "x", []string{}, "one or more xnames to get the status for")
150+
pcsStatusListCmd.Flags().VarP(&powerFilter, "powerfilter", "p", "filter results by power state (on, off, undefined)")
151+
pcsStatusListCmd.RegisterFlagCompletionFunc("powerfilter", pcsStatusListPowerFilterCompletion)
152+
pcsStatusListCmd.Flags().VarP(&mgmtFilter, "mgmtfilter", "m", "filter results by management state (available, unavailable)")
153+
pcsStatusListCmd.RegisterFlagCompletionFunc("mgmtfilter", pcsStatusListMgmtFilterCompletion)
154+
155+
pcsStatusListCmd.Flags().VarP(&formatOutput, "format-output", "F", "format of output printed to standard output (json,json-pretty,yaml)")
156+
pcsStatusListCmd.RegisterFlagCompletionFunc("format-output", completionFormatData)
157+
158+
pcsStatusCmd.AddCommand(pcsStatusListCmd)
159+
}

cmd/pcs-status-show.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// This source code is licensed under the license found in the LICENSE file at
2+
// the root directory of this source tree.
3+
package cmd
4+
5+
import (
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"os"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/OpenCHAMI/ochami/internal/log"
14+
"github.com/OpenCHAMI/ochami/pkg/client"
15+
"github.com/OpenCHAMI/ochami/pkg/format"
16+
)
17+
18+
type statusResponse struct {
19+
Status []map[string]interface{} `json:"status"`
20+
}
21+
22+
// pcsStatusShowCmd represents the "pcs status show" command
23+
var pcsStatusShowCmd = &cobra.Command{
24+
Use: "show <xname>",
25+
Args: cobra.ExactArgs(1),
26+
Short: "Show power status of target component",
27+
Long: ` Show power status of target component .
28+
29+
See ochami-pcs(1) for more details.`,
30+
Example: ` # show power status of component
31+
ochami pcs status show x3000c0s15b0`,
32+
Run: func(cmd *cobra.Command, args []string) {
33+
xname := args[0]
34+
35+
// Create client to use for requests
36+
pcsClient := pcsGetClient(cmd)
37+
38+
// Handle token for this command
39+
handleToken(cmd)
40+
41+
// Get status
42+
statusHttpEnv, err := pcsClient.GetStatus([]string{xname}, "", "", token)
43+
if err != nil {
44+
if errors.Is(err, client.UnsuccessfulHTTPError) {
45+
log.Logger.Error().Err(err).Msg("PCS status request yielded unsuccessful HTTP response")
46+
} else {
47+
log.Logger.Error().Err(err).Msg("failed to get power status")
48+
}
49+
logHelpError(cmd)
50+
os.Exit(1)
51+
}
52+
53+
var output statusResponse
54+
55+
err = json.Unmarshal(statusHttpEnv.Body, &output)
56+
if err != nil {
57+
log.Logger.Error().Err(err).Msg("failed to unmarshal status")
58+
logHelpError(cmd)
59+
os.Exit(1)
60+
}
61+
62+
// Check if status array is empty
63+
if len(output.Status) == 0 {
64+
log.Logger.Error().Msg("no status found for the specified component")
65+
logHelpError(cmd)
66+
os.Exit(1)
67+
}
68+
69+
// Print output just for first element in status array
70+
if outBytes, err := format.MarshalData(output.Status[0], formatOutput); err != nil {
71+
log.Logger.Error().Err(err).Msg("failed to format output")
72+
logHelpError(cmd)
73+
os.Exit(1)
74+
} else {
75+
fmt.Println(string(outBytes))
76+
}
77+
},
78+
}
79+
80+
func init() {
81+
pcsStatusShowCmd.Flags().VarP(&formatOutput, "format-output", "F", "format of output printed to standard output (json,json-pretty,yaml)")
82+
83+
pcsStatusShowCmd.RegisterFlagCompletionFunc("format-output", completionFormatData)
84+
85+
pcsStatusCmd.AddCommand(pcsStatusShowCmd)
86+
}

cmd/pcs-status.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This source code is licensed under the license found in the LICENSE file at
2+
// the root directory of this source tree.
3+
package cmd
4+
5+
import (
6+
"github.com/spf13/cobra"
7+
)
8+
9+
// pcsStatusCmd represents the "pcs status" command
10+
var pcsStatusCmd = &cobra.Command{
11+
Use: "status",
12+
Args: cobra.NoArgs,
13+
Short: "Manage PCS status",
14+
Run: func(cmd *cobra.Command, args []string) {
15+
if len(args) == 0 {
16+
printUsageHandleError(cmd)
17+
}
18+
},
19+
}
20+
21+
func init() {
22+
pcsCmd.AddCommand(pcsStatusCmd)
23+
}

cmd/pcs-transition-start.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"github.com/OpenCHAMI/ochami/pkg/format"
1616
)
1717

18-
var xnames []string
1918
var operation string
2019

2120
// validOperations returns a list of valid PCS operations

cmd/pcs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"github.com/OpenCHAMI/ochami/pkg/client/pcs"
1212
)
1313

14+
// xnames holds the list of xnames provided via command line flag
15+
var xnames []string
16+
1417
// pcsGetClient sets up the PCS client with the PCS base URI and certificates
1518
// (if necessary) and returns it. This function is used by each subcommand.
1619
func pcsGetClient(cmd *cobra.Command) *pcs.PCSClient {

man/ochami-pcs.1.sc

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,53 @@ Subcommands for this command are as follows:
7373

7474
## status
7575

76-
Manage power status.
76+
Display power status.
77+
78+
Subcommands for this command are as follows:
79+
80+
*show* [-F _format_] _xname_
81+
Show the power status of a component.
82+
83+
This command accepts the following options:
84+
85+
*-F, --format-output* _format_
86+
Output response data in specified _format_. Supported values are:
87+
88+
- _json_ (default)
89+
- _json-pretty_
90+
- _yaml_
91+
92+
*_xname_*
93+
XName of the component to show the power status for.
94+
95+
*list* [-F _format_]
96+
List the power status of all components.
97+
98+
This command accepts the following options:
99+
100+
*-F, --format-output* _format_
101+
Output response data in specified _format_. Supported values are:
102+
103+
- _json_ (default)
104+
- _json-pretty_
105+
- _yaml_
106+
107+
*-x, --xname* _xname_,...
108+
Comma-separated list of xnames to show power status for. If not
109+
specified, status for all components will be shown.
110+
111+
*-p, --powerfilter* _state_
112+
power state filter. Supported values are:
113+
114+
- _on_
115+
- _off_
116+
- _undefined_
117+
118+
*-m, --mgmtfilter* _state_
119+
management state filter. Supported values are:
120+
121+
- _available_
122+
- _unavailable_
77123

78124
## transitions
79125

pkg/client/pcs/pcs.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
PCSRelpathReadiness = "/readiness"
1818
PCSRelpathHealth = "/health"
1919
PCSTransitions = "/transitions"
20+
PCSStatus = "/power-status"
2021
)
2122

2223
// PCSClient is an OchamiClient that has its BasePath set configured to the one
@@ -224,3 +225,41 @@ func (pc *PCSClient) DeleteTransition(id string, token string) (client.HTTPEnvel
224225

225226
return henv, err
226227
}
228+
229+
// GetTransitions is a wrapper function around OchamiClient.GetData to
230+
// hit the /power-status endpoint
231+
func (pc *PCSClient) GetStatus(xnames []string, powerStateFilter string, mgmtStateFilter string, token string) (client.HTTPEnvelope, error) {
232+
var (
233+
henv client.HTTPEnvelope
234+
err error
235+
)
236+
237+
headers := client.NewHTTPHeaders()
238+
if token != "" {
239+
if err := headers.SetAuthorization(token); err != nil {
240+
return henv, fmt.Errorf("GetStatus(): error setting token in HTTP headers: %w", err)
241+
}
242+
}
243+
244+
values := url.Values{}
245+
for _, xname := range xnames {
246+
values.Add("xname", xname)
247+
}
248+
249+
if powerStateFilter != "" {
250+
values.Add("powerStateFilter", powerStateFilter)
251+
}
252+
253+
if mgmtStateFilter != "" {
254+
values.Add("managementStateFilter", mgmtStateFilter)
255+
}
256+
257+
query := values.Encode()
258+
259+
henv, err = pc.GetData(PCSStatus, query, headers)
260+
if err != nil {
261+
err = fmt.Errorf("GetStatus(): error getting power state: %w", err)
262+
}
263+
264+
return henv, err
265+
}

0 commit comments

Comments
 (0)