Skip to content

Commit e6e4501

Browse files
resolve conflict
2 parents 72238da + 943cb8c commit e6e4501

File tree

12 files changed

+759
-13
lines changed

12 files changed

+759
-13
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,25 @@ Flags:
132132
-c, --cluster string set the cluster
133133
-h, --help help for info
134134
135+
Global Flags:
136+
--config string set the location of the config file (YAML or JSON)
137+
138+
##### status
139+
140+
Show the status reported by the OSCAR Manager API, including node metrics, deployment readiness and MinIO statistics.
141+
142+
```
143+
Usage:
144+
oscar-cli cluster status [flags]
145+
146+
Aliases:
147+
status, s
148+
149+
Flags:
150+
-c, --cluster string set the cluster
151+
-o, --output string output format (yaml or json) (default "yaml")
152+
-h, --help help for status
153+
135154
Global Flags:
136155
--config string set the location of the config file (YAML or JSON)
137156
```

cmd/cluster.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func makeClusterCmd() *cobra.Command {
4242
clusterCmd.AddCommand(makeClusterAddCmd())
4343
clusterCmd.AddCommand(makeClusterRemoveCmd())
4444
clusterCmd.AddCommand(makeClusterInfoCmd())
45+
clusterCmd.AddCommand(makeClusterStatusCmd())
4546
clusterCmd.AddCommand(makeClusterListCmd())
4647
clusterCmd.AddCommand(makeClusterDefaultCmd())
4748

cmd/cluster_status.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
Copyright (C) GRyCAP - I3M - UPV
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
23+
"github.com/goccy/go-yaml"
24+
"github.com/grycap/oscar-cli/pkg/cluster"
25+
"github.com/grycap/oscar-cli/pkg/config"
26+
"github.com/spf13/cobra"
27+
)
28+
29+
func clusterStatusFunc(cmd *cobra.Command, args []string) error {
30+
conf, err := config.ReadConfig(configPath)
31+
if err != nil {
32+
return err
33+
}
34+
35+
clusterName, err := getCluster(cmd, conf)
36+
if err != nil {
37+
return err
38+
}
39+
40+
status, err := conf.Oscar[clusterName].GetClusterStatus()
41+
if err != nil {
42+
return err
43+
}
44+
45+
output, _ := cmd.Flags().GetString("output")
46+
switch output {
47+
case "json":
48+
if err := clusterInfoPrintJSON(cmd, status); err != nil {
49+
return err
50+
}
51+
default:
52+
outputyaml, err := yaml.Marshal(status)
53+
if err != nil {
54+
return fmt.Errorf("failed to serialize cluster status: %w", err)
55+
}
56+
fmt.Print(string(outputyaml))
57+
58+
}
59+
60+
return nil
61+
}
62+
63+
func clusterInfoPrintJSON(cmd *cobra.Command, objects cluster.StatusInfo) error {
64+
encoder := json.NewEncoder(cmd.OutOrStdout())
65+
encoder.SetIndent("", " ")
66+
return encoder.Encode(objects)
67+
}
68+
69+
func makeClusterStatusCmd() *cobra.Command {
70+
clusterStatusCmd := &cobra.Command{
71+
Use: "status",
72+
Short: "Show status information of an OSCAR cluster",
73+
Args: cobra.NoArgs,
74+
Aliases: []string{"s"},
75+
RunE: clusterStatusFunc,
76+
}
77+
78+
clusterStatusCmd.Flags().StringP("cluster", "c", "", "set the cluster")
79+
clusterStatusCmd.Flags().StringP("output", "o", "table", "output format (json)")
80+
81+
return clusterStatusCmd
82+
}

cmd/cluster_status_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/grycap/oscar-cli/pkg/cluster"
12+
)
13+
14+
func TestClusterStatusCommandPrintsStatus(t *testing.T) {
15+
expected := cluster.StatusInfo{
16+
Cluster: cluster.ClusterStatus{
17+
NodesCount: 1,
18+
Metrics: cluster.ClusterMetrics{
19+
CPU: cluster.CPUMetrics{
20+
TotalFreeCores: 2,
21+
MaxFreeOnNodeCores: 2,
22+
},
23+
},
24+
},
25+
Oscar: cluster.OscarStatus{
26+
DeploymentName: "oscar-manager",
27+
Deployment: cluster.OscarDeployment{
28+
CreationTimestamp: time.Unix(1700000000, 0).UTC(),
29+
},
30+
},
31+
MinIO: cluster.MinioStatus{
32+
BucketsCount: 4,
33+
TotalObjects: 10,
34+
},
35+
}
36+
37+
const (
38+
username = "user"
39+
password = "pass"
40+
)
41+
42+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43+
if r.URL.Path != "/system/status" {
44+
http.NotFound(w, r)
45+
return
46+
}
47+
gotUser, gotPass, ok := r.BasicAuth()
48+
if !ok || gotUser != username || gotPass != password {
49+
http.Error(w, "unauthorized", http.StatusUnauthorized)
50+
return
51+
}
52+
if err := json.NewEncoder(w).Encode(expected); err != nil {
53+
t.Fatalf("encoding status: %v", err)
54+
}
55+
}))
56+
defer server.Close()
57+
58+
configFile := writeConfigFile(t, "alpha", server.URL)
59+
60+
stdout, stderr, err := runCommand(t,
61+
"cluster", "--config", configFile,
62+
"status",
63+
"--cluster", "alpha",
64+
)
65+
if err != nil {
66+
t.Fatalf("cluster status command returned error: %v", err)
67+
}
68+
if stderr != "" {
69+
t.Fatalf("expected empty stderr, got %q", stderr)
70+
}
71+
if !strings.Contains(stdout, "nodes_count: 1") {
72+
t.Fatalf("expected nodes_count in output, got %q", stdout)
73+
}
74+
if !strings.Contains(stdout, "deployment_name: oscar-manager") {
75+
t.Fatalf("expected deployment_name in output, got %q", stdout)
76+
}
77+
if !strings.Contains(stdout, "buckets_count: 4") {
78+
t.Fatalf("expected buckets_count in output, got %q", stdout)
79+
}
80+
}
81+
82+
func TestClusterStatusCommandError(t *testing.T) {
83+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
84+
http.Error(w, "boom", http.StatusInternalServerError)
85+
}))
86+
defer server.Close()
87+
88+
configFile := writeConfigFile(t, "alpha", server.URL)
89+
90+
_, _, err := runCommand(t,
91+
"cluster", "--config", configFile,
92+
"status",
93+
"--cluster", "alpha",
94+
)
95+
if err == nil {
96+
t.Fatalf("expected error, got nil")
97+
}
98+
if err.Error() != "boom\n" {
99+
t.Fatalf("expected boom error, got %v", err)
100+
}
101+
}

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ require (
2222
require (
2323
github.com/gdamore/tcell/v2 v2.8.1
2424
github.com/golang-jwt/jwt/v5 v5.2.2
25-
github.com/grycap/oscar/v3 v3.3.0
25+
github.com/grycap/oscar/v3 v3.6.5
2626
github.com/indigo-dc/liboidcagent-go v0.3.0
2727
github.com/rivo/tview v0.42.0
2828
golang.org/x/term v0.28.0
29+
gopkg.in/yaml.v3 v3.0.1
2930
)
3031

3132
require (
@@ -58,14 +59,13 @@ require (
5859
github.com/rivo/uniseg v0.4.7 // indirect
5960
github.com/spf13/pflag v1.0.5 // indirect
6061
golang.org/x/crypto v0.31.0 // indirect
61-
golang.org/x/oauth2 v0.10.0 // indirect
62+
golang.org/x/oauth2 v0.27.0 // indirect
6263
golang.org/x/time v0.3.0 // indirect
6364
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
6465
google.golang.org/appengine v1.6.7 // indirect
6566
google.golang.org/protobuf v1.33.0 // indirect
6667
gopkg.in/inf.v0 v0.9.1 // indirect
6768
gopkg.in/yaml.v2 v2.4.0 // indirect
68-
gopkg.in/yaml.v3 v3.0.1 // indirect
6969
k8s.io/apimachinery v0.29.2 // indirect
7070
k8s.io/client-go v0.29.2 // indirect
7171
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ github.com/grycap/cdmi-client-go v0.1.1 h1:kHIrrLhvaCD0VyzEa5HOg7d/VgRE11yh9Ztdy
7474
github.com/grycap/cdmi-client-go v0.1.1/go.mod h1:ZqWeQS3YBJVXxg3HOIkAu1MLNJ4+7s848CyIPMFT5Gc=
7575
github.com/grycap/oscar/v3 v3.3.0 h1:+zDkUu6IryJ7Xq2QGNDkDCvhMrVhpjAU4cmiW2or+wc=
7676
github.com/grycap/oscar/v3 v3.3.0/go.mod h1:EN8kr1DLZc99llMJvXk4vnfVKQzEMLrnWdqaEKdg8YI=
77+
github.com/grycap/oscar/v3 v3.6.5 h1:iQDKY7Vqv/iWGLzyFoxONYQsHA8WunDhyOXP2JKGUis=
78+
github.com/grycap/oscar/v3 v3.6.5/go.mod h1:Js7D2jFlI5oCYw0QBFp1i3miRYtFLHF6HEALDbSoAis=
7779
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
7880
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
7981
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@@ -197,6 +199,8 @@ golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
197199
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
198200
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
199201
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
202+
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
203+
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
200204
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
201205
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
202206
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

pkg/cluster/cluster.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636

3737
const infoPath = "/system/info"
3838
const configPath = "/system/config"
39+
const statusPath = "/system/status"
3940
const _DEFAULT_TIMEOUT = 20
4041

4142
var (
@@ -242,6 +243,41 @@ func (cluster *Cluster) GetClusterConfig() (cfg types.Config, err error) {
242243
return cfg, nil
243244
}
244245

246+
// GetClusterStatus returns the status of an OSCAR cluster
247+
func (cluster *Cluster) GetClusterStatus() (status StatusInfo, err error) {
248+
getStatusURL, err := url.Parse(cluster.Endpoint)
249+
if err != nil {
250+
return status, ErrParsingEndpoint
251+
}
252+
getStatusURL.Path = path.Join(getStatusURL.Path, statusPath)
253+
254+
req, err := http.NewRequest(http.MethodGet, getStatusURL.String(), nil)
255+
if err != nil {
256+
return status, ErrMakingRequest
257+
}
258+
259+
client, err := cluster.GetClientSafe()
260+
if err != nil {
261+
return status, err
262+
}
263+
264+
res, err := client.Do(req)
265+
if err != nil {
266+
return status, ErrSendingRequest
267+
}
268+
defer res.Body.Close()
269+
270+
if err := CheckStatusCode(res); err != nil {
271+
return status, err
272+
}
273+
274+
if err := json.NewDecoder(res.Body).Decode(&status); err != nil {
275+
return status, fmt.Errorf("unable to parse cluster status response: %w", err)
276+
}
277+
278+
return status, nil
279+
}
280+
245281
// CheckStatusCode checks if a cluster response is valid and returns an appropriate error if not
246282
func CheckStatusCode(res *http.Response) error {
247283
if res.StatusCode >= 200 && res.StatusCode <= 204 {

0 commit comments

Comments
 (0)