master
go 205 lines 5.44 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package client
4
5 import (
6 "encoding/json"
7 "fmt"
8 "net/http"
9 "sync"
10
11 "github.com/netdata/netdata/go/plugins/pkg/web"
12 )
13
14 // Session: https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.cis.session
15 // Health: https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.appliance.health
16
17 const (
18 pathCISSession = "/rest/com/vmware/cis/session"
19 pathHealthSystem = "/rest/appliance/health/system"
20 pathHealthSwap = "/rest/appliance/health/swap"
21 pathHealthStorage = "/rest/appliance/health/storage"
22 pathHealthSoftwarePackager = "/rest/appliance/health/software-packages"
23 pathHealthMem = "/rest/appliance/health/mem"
24 pathHealthLoad = "/rest/appliance/health/load"
25 pathHealthDatabaseStorage = "/rest/appliance/health/database-storage"
26 pathHealthApplMgmt = "/rest/appliance/health/applmgmt"
27
28 apiSessIDKey = "vmware-api-session-id"
29 )
30
31 type sessionToken struct {
32 m *sync.RWMutex
33 id string
34 }
35
36 func (s *sessionToken) set(id string) {
37 s.m.Lock()
38 defer s.m.Unlock()
39 s.id = id
40 }
41
42 func (s *sessionToken) get() string {
43 s.m.RLock()
44 defer s.m.RUnlock()
45 return s.id
46 }
47
48 func New(httpClient *http.Client, url, username, password string) *Client {
49 if httpClient == nil {
50 httpClient = &http.Client{}
51 }
52 return &Client{
53 httpClient: httpClient,
54 url: url,
55 username: username,
56 password: password,
57 token: &sessionToken{m: new(sync.RWMutex)},
58 }
59 }
60
61 type Client struct {
62 httpClient *http.Client
63
64 url string
65 username string
66 password string
67
68 token *sessionToken
69 }
70
71 // Login creates a session with the API. This operation exchanges user credentials supplied in the security context
72 // for a session identifier that is to be used for authenticating subsequent calls.
73 func (c *Client) Login() error {
74 req := web.RequestConfig{
75 URL: fmt.Sprintf("%s%s", c.url, pathCISSession),
76 Username: c.username,
77 Password: c.password,
78 Method: http.MethodPost,
79 }
80 s := struct{ Value string }{}
81
82 err := c.doOKWithDecode(req, &s)
83 if err == nil {
84 c.token.set(s.Value)
85 }
86 return err
87 }
88
89 // Logout terminates the validity of a session token.
90 func (c *Client) Logout() error {
91 req := web.RequestConfig{
92 URL: fmt.Sprintf("%s%s", c.url, pathCISSession),
93 Method: http.MethodDelete,
94 Headers: map[string]string{apiSessIDKey: c.token.get()},
95 }
96
97 resp, err := c.doOK(req)
98 web.CloseBody(resp)
99 c.token.set("")
100 return err
101 }
102
103 // Ping sent a request to VCSA server to ensure the link is operating.
104 // In case of 401 error Ping tries to re authenticate.
105 func (c *Client) Ping() error {
106 req := web.RequestConfig{
107 URL: fmt.Sprintf("%s%s?~action=get", c.url, pathCISSession),
108 Method: http.MethodPost,
109 Headers: map[string]string{apiSessIDKey: c.token.get()},
110 }
111 resp, err := c.doOK(req)
112 defer web.CloseBody(resp)
113 if resp != nil && resp.StatusCode == http.StatusUnauthorized {
114 return c.Login()
115 }
116 return err
117 }
118
119 func (c *Client) health(urlPath string) (string, error) {
120 req := web.RequestConfig{
121 URL: fmt.Sprintf("%s%s", c.url, urlPath),
122 Headers: map[string]string{apiSessIDKey: c.token.get()},
123 }
124 s := struct{ Value string }{}
125 err := c.doOKWithDecode(req, &s)
126 return s.Value, err
127 }
128
129 // ApplMgmt provides health status of applmgmt services.
130 func (c *Client) ApplMgmt() (string, error) {
131 return c.health(pathHealthApplMgmt)
132 }
133
134 // DatabaseStorage provides health status of database storage health.
135 func (c *Client) DatabaseStorage() (string, error) {
136 return c.health(pathHealthDatabaseStorage)
137 }
138
139 // Load provides health status of load health.
140 func (c *Client) Load() (string, error) {
141 return c.health(pathHealthLoad)
142 }
143
144 // Mem provides health status of memory health.
145 func (c *Client) Mem() (string, error) {
146 return c.health(pathHealthMem)
147 }
148
149 // SoftwarePackages provides information on available software updates available in remote VUM repository.
150 // Red indicates that security updates are available.
151 // Orange indicates that non-security updates are available.
152 // Green indicates that there are no updates available.
153 // Gray indicates that there was an error retrieving information on software updates.
154 func (c *Client) SoftwarePackages() (string, error) {
155 return c.health(pathHealthSoftwarePackager)
156 }
157
158 // Storage provides health status of storage health.
159 func (c *Client) Storage() (string, error) {
160 return c.health(pathHealthStorage)
161 }
162
163 // Swap provides health status of swap health.
164 func (c *Client) Swap() (string, error) {
165 return c.health(pathHealthSwap)
166 }
167
168 // System provides overall health of system.
169 func (c *Client) System() (string, error) {
170 return c.health(pathHealthSystem)
171 }
172
173 func (c *Client) do(req web.RequestConfig) (*http.Response, error) {
174 httpReq, err := web.NewHTTPRequest(req)
175 if err != nil {
176 return nil, fmt.Errorf("error on creating http request to %s : %v", req.URL, err)
177 }
178 return c.httpClient.Do(httpReq)
179 }
180
181 func (c *Client) doOK(req web.RequestConfig) (*http.Response, error) {
182 resp, err := c.do(req)
183 if err != nil {
184 return nil, err
185 }
186
187 if resp.StatusCode != http.StatusOK {
188 return resp, fmt.Errorf("%s returned %d", req.URL, resp.StatusCode)
189 }
190 return resp, nil
191 }
192
193 func (c *Client) doOKWithDecode(req web.RequestConfig, dst any) error {
194 resp, err := c.doOK(req)
195 defer web.CloseBody(resp)
196 if err != nil {
197 return err
198 }
199
200 err = json.NewDecoder(resp.Body).Decode(dst)
201 if err != nil {
202 return fmt.Errorf("error on decoding response from %s : %v", req.URL, err)
203 }
204 return nil
205 }