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