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  }