summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/vcsa
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 11:19:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 12:07:37 +0000
commitb485aab7e71c1625cfc27e0f92c9509f42378458 (patch)
treeae9abe108601079d1679194de237c9a435ae5b55 /src/go/collectors/go.d.plugin/modules/vcsa
parentAdding upstream version 1.44.3. (diff)
downloadnetdata-b485aab7e71c1625cfc27e0f92c9509f42378458.tar.xz
netdata-b485aab7e71c1625cfc27e0f92c9509f42378458.zip
Adding upstream version 1.45.3+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/collectors/go.d.plugin/modules/vcsa')
l---------src/go/collectors/go.d.plugin/modules/vcsa/README.md1
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/charts.go138
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/client/client.go213
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/client/client_test.go288
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/collect.go95
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/config_schema.json168
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/init.go29
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/integrations/vcenter_server_appliance.md257
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/metadata.yaml346
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.json20
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.yaml17
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/vcsa.go137
-rw-r--r--src/go/collectors/go.d.plugin/modules/vcsa/vcsa_test.go304
13 files changed, 2013 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/README.md b/src/go/collectors/go.d.plugin/modules/vcsa/README.md
new file mode 120000
index 000000000..0d00f4673
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/README.md
@@ -0,0 +1 @@
+integrations/vcenter_server_appliance.md \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/charts.go b/src/go/collectors/go.d.plugin/modules/vcsa/charts.go
new file mode 100644
index 000000000..306b6a57b
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/charts.go
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package vcsa
+
+import "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+var (
+ vcsaHealthCharts = module.Charts{
+ systemHealthStatus.Copy(),
+ applMgmtHealthChart.Copy(),
+ loadHealthChart.Copy(),
+ memHealthChart.Copy(),
+ swapHealthChart.Copy(),
+ dbStorageHealthChart.Copy(),
+ storageHealthChart.Copy(),
+ softwarePackagesHealthChart.Copy(),
+ }
+
+ systemHealthStatus = module.Chart{
+ ID: "system_health_status",
+ Title: "VCSA Overall System health status",
+ Units: "status",
+ Fam: "system",
+ Ctx: "vcsa.system_health_status",
+ Dims: module.Dims{
+ {ID: "system_status_green", Name: "green"},
+ {ID: "system_status_red", Name: "red"},
+ {ID: "system_status_yellow", Name: "yellow"},
+ {ID: "system_status_orange", Name: "orange"},
+ {ID: "system_status_gray", Name: "gray"},
+ {ID: "system_status_unknown", Name: "unknown"},
+ },
+ }
+ applMgmtHealthChart = module.Chart{
+ ID: "applmgmt_health_status",
+ Title: "VCSA Appliance Management Service (applmgmt) health status",
+ Units: "status",
+ Fam: "appliance mgmt service",
+ Ctx: "vcsa.applmgmt_health_status",
+ Dims: module.Dims{
+ {ID: "applmgmt_status_green", Name: "green"},
+ {ID: "applmgmt_status_red", Name: "red"},
+ {ID: "applmgmt_status_yellow", Name: "yellow"},
+ {ID: "applmgmt_status_orange", Name: "orange"},
+ {ID: "applmgmt_status_gray", Name: "gray"},
+ {ID: "applmgmt_status_unknown", Name: "unknown"},
+ },
+ }
+ loadHealthChart = module.Chart{
+ ID: "load_health_status",
+ Title: "VCSA Load health status",
+ Units: "status",
+ Fam: "load",
+ Ctx: "vcsa.load_health_status",
+ Dims: module.Dims{
+ {ID: "load_status_green", Name: "green"},
+ {ID: "load_status_red", Name: "red"},
+ {ID: "load_status_yellow", Name: "yellow"},
+ {ID: "load_status_orange", Name: "orange"},
+ {ID: "load_status_gray", Name: "gray"},
+ {ID: "load_status_unknown", Name: "unknown"},
+ },
+ }
+ memHealthChart = module.Chart{
+ ID: "mem_health_status",
+ Title: "VCSA Memory health status",
+ Units: "status",
+ Fam: "mem",
+ Ctx: "vcsa.mem_health_status",
+ Dims: module.Dims{
+ {ID: "mem_status_green", Name: "green"},
+ {ID: "mem_status_red", Name: "red"},
+ {ID: "mem_status_yellow", Name: "yellow"},
+ {ID: "mem_status_orange", Name: "orange"},
+ {ID: "mem_status_gray", Name: "gray"},
+ {ID: "mem_status_unknown", Name: "unknown"},
+ },
+ }
+ swapHealthChart = module.Chart{
+ ID: "swap_health_status",
+ Title: "VCSA Swap health status",
+ Units: "status",
+ Fam: "swap",
+ Ctx: "vcsa.swap_health_status",
+ Dims: module.Dims{
+ {ID: "swap_status_green", Name: "green"},
+ {ID: "swap_status_red", Name: "red"},
+ {ID: "swap_status_yellow", Name: "yellow"},
+ {ID: "swap_status_orange", Name: "orange"},
+ {ID: "swap_status_gray", Name: "gray"},
+ {ID: "swap_status_unknown", Name: "unknown"},
+ },
+ }
+ dbStorageHealthChart = module.Chart{
+ ID: "database_storage_health_status",
+ Title: "VCSA Database Storage health status",
+ Units: "status",
+ Fam: "db storage",
+ Ctx: "vcsa.database_storage_health_status",
+ Dims: module.Dims{
+ {ID: "database_storage_status_green", Name: "green"},
+ {ID: "database_storage_status_red", Name: "red"},
+ {ID: "database_storage_status_yellow", Name: "yellow"},
+ {ID: "database_storage_status_orange", Name: "orange"},
+ {ID: "database_storage_status_gray", Name: "gray"},
+ {ID: "database_storage_status_unknown", Name: "unknown"},
+ },
+ }
+ storageHealthChart = module.Chart{
+ ID: "storage_health_status",
+ Title: "VCSA Storage health status",
+ Units: "status",
+ Fam: "storage",
+ Ctx: "vcsa.storage_health_status",
+ Dims: module.Dims{
+ {ID: "storage_status_green", Name: "green"},
+ {ID: "storage_status_red", Name: "red"},
+ {ID: "storage_status_yellow", Name: "yellow"},
+ {ID: "storage_status_orange", Name: "orange"},
+ {ID: "storage_status_gray", Name: "gray"},
+ {ID: "storage_status_unknown", Name: "unknown"},
+ },
+ }
+ softwarePackagesHealthChart = module.Chart{
+ ID: "software_packages_health_status",
+ Title: "VCSA Software Updates health status",
+ Units: "status",
+ Fam: "software packages",
+ Ctx: "vcsa.software_packages_health_status",
+ Dims: module.Dims{
+ {ID: "software_packages_status_green", Name: "green"},
+ {ID: "software_packages_status_red", Name: "red"},
+ {ID: "software_packages_status_orange", Name: "orange"},
+ {ID: "software_packages_status_gray", Name: "gray"},
+ {ID: "software_packages_status_unknown", Name: "unknown"},
+ },
+ }
+)
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/client/client.go b/src/go/collectors/go.d.plugin/modules/vcsa/client/client.go
new file mode 100644
index 000000000..64f53ff44
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/client/client.go
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package client
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "sync"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+)
+
+// Session: https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.cis.session
+// Health: https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.appliance.health
+
+const (
+ pathCISSession = "/rest/com/vmware/cis/session"
+ pathHealthSystem = "/rest/appliance/health/system"
+ pathHealthSwap = "/rest/appliance/health/swap"
+ pathHealthStorage = "/rest/appliance/health/storage"
+ pathHealthSoftwarePackager = "/rest/appliance/health/software-packages"
+ pathHealthMem = "/rest/appliance/health/mem"
+ pathHealthLoad = "/rest/appliance/health/load"
+ pathHealthDatabaseStorage = "/rest/appliance/health/database-storage"
+ pathHealthApplMgmt = "/rest/appliance/health/applmgmt"
+
+ apiSessIDKey = "vmware-api-session-id"
+)
+
+type sessionToken struct {
+ m *sync.RWMutex
+ id string
+}
+
+func (s *sessionToken) set(id string) {
+ s.m.Lock()
+ defer s.m.Unlock()
+ s.id = id
+}
+
+func (s *sessionToken) get() string {
+ s.m.RLock()
+ defer s.m.RUnlock()
+ return s.id
+}
+
+func New(httpClient *http.Client, url, username, password string) *Client {
+ if httpClient == nil {
+ httpClient = &http.Client{}
+ }
+ return &Client{
+ httpClient: httpClient,
+ url: url,
+ username: username,
+ password: password,
+ token: &sessionToken{m: new(sync.RWMutex)},
+ }
+}
+
+type Client struct {
+ httpClient *http.Client
+
+ url string
+ username string
+ password string
+
+ token *sessionToken
+}
+
+// Login creates a session with the API. This operation exchanges user credentials supplied in the security context
+// for a session identifier that is to be used for authenticating subsequent calls.
+func (c *Client) Login() error {
+ req := web.Request{
+ URL: fmt.Sprintf("%s%s", c.url, pathCISSession),
+ Username: c.username,
+ Password: c.password,
+ Method: http.MethodPost,
+ }
+ s := struct{ Value string }{}
+
+ err := c.doOKWithDecode(req, &s)
+ if err == nil {
+ c.token.set(s.Value)
+ }
+ return err
+}
+
+// Logout terminates the validity of a session token.
+func (c *Client) Logout() error {
+ req := web.Request{
+ URL: fmt.Sprintf("%s%s", c.url, pathCISSession),
+ Method: http.MethodDelete,
+ Headers: map[string]string{apiSessIDKey: c.token.get()},
+ }
+
+ resp, err := c.doOK(req)
+ closeBody(resp)
+ c.token.set("")
+ return err
+}
+
+// Ping sent a request to VCSA server to ensure the link is operating.
+// In case of 401 error Ping tries to re authenticate.
+func (c *Client) Ping() error {
+ req := web.Request{
+ URL: fmt.Sprintf("%s%s?~action=get", c.url, pathCISSession),
+ Method: http.MethodPost,
+ Headers: map[string]string{apiSessIDKey: c.token.get()},
+ }
+ resp, err := c.doOK(req)
+ defer closeBody(resp)
+ if resp != nil && resp.StatusCode == http.StatusUnauthorized {
+ return c.Login()
+ }
+ return err
+}
+
+func (c *Client) health(urlPath string) (string, error) {
+ req := web.Request{
+ URL: fmt.Sprintf("%s%s", c.url, urlPath),
+ Headers: map[string]string{apiSessIDKey: c.token.get()},
+ }
+ s := struct{ Value string }{}
+ err := c.doOKWithDecode(req, &s)
+ return s.Value, err
+}
+
+// ApplMgmt provides health status of applmgmt services.
+func (c *Client) ApplMgmt() (string, error) {
+ return c.health(pathHealthApplMgmt)
+}
+
+// DatabaseStorage provides health status of database storage health.
+func (c *Client) DatabaseStorage() (string, error) {
+ return c.health(pathHealthDatabaseStorage)
+}
+
+// Load provides health status of load health.
+func (c *Client) Load() (string, error) {
+ return c.health(pathHealthLoad)
+}
+
+// Mem provides health status of memory health.
+func (c *Client) Mem() (string, error) {
+ return c.health(pathHealthMem)
+}
+
+// SoftwarePackages provides information on available software updates available in remote VUM repository.
+// Red indicates that security updates are available.
+// Orange indicates that non-security updates are available.
+// Green indicates that there are no updates available.
+// Gray indicates that there was an error retrieving information on software updates.
+func (c *Client) SoftwarePackages() (string, error) {
+ return c.health(pathHealthSoftwarePackager)
+}
+
+// Storage provides health status of storage health.
+func (c *Client) Storage() (string, error) {
+ return c.health(pathHealthStorage)
+}
+
+// Swap provides health status of swap health.
+func (c *Client) Swap() (string, error) {
+ return c.health(pathHealthSwap)
+}
+
+// System provides overall health of system.
+func (c *Client) System() (string, error) {
+ return c.health(pathHealthSystem)
+}
+
+func (c *Client) do(req web.Request) (*http.Response, error) {
+ httpReq, err := web.NewHTTPRequest(req)
+ if err != nil {
+ return nil, fmt.Errorf("error on creating http request to %s : %v", req.URL, err)
+ }
+ return c.httpClient.Do(httpReq)
+}
+
+func (c *Client) doOK(req web.Request) (*http.Response, error) {
+ resp, err := c.do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return resp, fmt.Errorf("%s returned %d", req.URL, resp.StatusCode)
+ }
+ return resp, nil
+}
+
+func (c *Client) doOKWithDecode(req web.Request, dst interface{}) error {
+ resp, err := c.doOK(req)
+ defer closeBody(resp)
+ if err != nil {
+ return err
+ }
+
+ err = json.NewDecoder(resp.Body).Decode(dst)
+ if err != nil {
+ return fmt.Errorf("error on decoding response from %s : %v", req.URL, err)
+ }
+ return nil
+}
+
+func closeBody(resp *http.Response) {
+ if resp != nil && resp.Body != nil {
+ _, _ = io.Copy(io.Discard, resp.Body)
+ _ = resp.Body.Close()
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/client/client_test.go b/src/go/collectors/go.d.plugin/modules/vcsa/client/client_test.go
new file mode 100644
index 000000000..379644b89
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/client/client_test.go
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package client
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+const (
+ testUser = "user"
+ testPass = "pass"
+ testSessToken = "sessToken"
+ testHealthValue = "green"
+)
+
+func newTestClient(srvURL string) *Client {
+ return New(nil, srvURL, testUser, testPass)
+}
+
+func TestClient_Login(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ assert.NoError(t, cl.Login())
+ assert.Equal(t, testSessToken, cl.token.get())
+}
+
+func TestClient_LoginWrongCredentials(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+ cl.username += "!"
+
+ assert.Error(t, cl.Login())
+}
+
+func TestClient_Logout(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ assert.NoError(t, cl.Login())
+ assert.NoError(t, cl.Logout())
+ assert.Zero(t, cl.token.get())
+}
+
+func TestClient_Ping(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ assert.NoError(t, cl.Ping())
+}
+
+func TestClient_PingWithReAuthentication(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ cl.token.set("")
+ assert.NoError(t, cl.Ping())
+ assert.Equal(t, testSessToken, cl.token.get())
+}
+
+func TestClient_ApplMgmt(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.ApplMgmt()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_DatabaseStorage(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.DatabaseStorage()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_Load(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.Load()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_Mem(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.Mem()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_SoftwarePackages(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.SoftwarePackages()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_Storage(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.Storage()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_Swap(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.Swap()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_System(t *testing.T) {
+ ts := newTestHTTPServer()
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ require.NoError(t, cl.Login())
+ v, err := cl.System()
+ assert.NoError(t, err)
+ assert.Equal(t, testHealthValue, v)
+}
+
+func TestClient_InvalidDataOnLogin(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, _ = w.Write([]byte("hello\n and goodbye!"))
+ }))
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ assert.Error(t, cl.Login())
+}
+
+func TestClient_404OnLogin(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(404)
+ }))
+ defer ts.Close()
+ cl := newTestClient(ts.URL)
+
+ assert.Error(t, cl.Login())
+}
+
+func newTestHTTPServer() *httptest.Server {
+ return httptest.NewServer(&mockVCSAServer{
+ username: testUser,
+ password: testPass,
+ sessionID: testSessToken,
+ })
+}
+
+type mockVCSAServer struct {
+ username string
+ password string
+ sessionID string
+}
+
+func (m mockVCSAServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ default:
+ w.WriteHeader(http.StatusNotFound)
+ case pathCISSession:
+ m.handleSession(w, r)
+ case
+ pathHealthApplMgmt,
+ pathHealthDatabaseStorage,
+ pathHealthLoad,
+ pathHealthMem,
+ pathHealthSoftwarePackager,
+ pathHealthStorage,
+ pathHealthSwap,
+ pathHealthSystem:
+ m.handleHealth(w, r)
+ }
+}
+
+func (m mockVCSAServer) handleHealth(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ if !m.isSessionAuthenticated(r) {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ s := struct{ Value string }{Value: testHealthValue}
+ b, _ := json.Marshal(s)
+ _, _ = w.Write(b)
+}
+
+func (m mockVCSAServer) handleSession(w http.ResponseWriter, r *http.Request) {
+ switch r.Method {
+ default:
+ w.WriteHeader(http.StatusBadRequest)
+ case http.MethodDelete:
+ m.handleSessionDelete(w, r)
+ case http.MethodPost:
+ if r.URL.RawQuery == "" {
+ m.handleSessionCreate(w, r)
+ } else {
+ m.handleSessionGet(w, r)
+ }
+ }
+}
+
+func (m mockVCSAServer) handleSessionCreate(w http.ResponseWriter, r *http.Request) {
+ if !m.isReqAuthenticated(r) {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ s := struct{ Value string }{Value: m.sessionID}
+ b, _ := json.Marshal(s)
+ _, _ = w.Write(b)
+}
+
+func (m mockVCSAServer) handleSessionGet(w http.ResponseWriter, r *http.Request) {
+ if !m.isSessionAuthenticated(r) {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ s := struct{ Value struct{ User string } }{Value: struct{ User string }{User: m.username}}
+ b, _ := json.Marshal(s)
+ _, _ = w.Write(b)
+}
+
+func (m mockVCSAServer) handleSessionDelete(w http.ResponseWriter, r *http.Request) {
+ if !m.isSessionAuthenticated(r) {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
+
+func (m mockVCSAServer) isReqAuthenticated(r *http.Request) bool {
+ u, p, ok := r.BasicAuth()
+ return ok && m.username == u && p == m.password
+}
+
+func (m mockVCSAServer) isSessionAuthenticated(r *http.Request) bool {
+ return r.Header.Get(apiSessIDKey) == m.sessionID
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/collect.go b/src/go/collectors/go.d.plugin/modules/vcsa/collect.go
new file mode 100644
index 000000000..8a734d9e8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/collect.go
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package vcsa
+
+import (
+ "sync"
+)
+
+var componentHealthStatuses = []string{"green", "red", "yellow", "orange", "gray"}
+var softwareHealthStatuses = []string{"green", "red", "orange", "gray"}
+
+type vcsaHealthStatus struct {
+ System *string
+ ApplMgmt *string
+ Load *string
+ Mem *string
+ Swap *string
+ DatabaseStorage *string
+ Storage *string
+ SoftwarePackages *string
+}
+
+func (vc *VCSA) collect() (map[string]int64, error) {
+ err := vc.client.Ping()
+ if err != nil {
+ return nil, err
+ }
+
+ var status vcsaHealthStatus
+ vc.scrapeHealth(&status)
+
+ mx := make(map[string]int64)
+
+ writeStatus(mx, "system", componentHealthStatuses, status.System)
+ writeStatus(mx, "applmgmt", componentHealthStatuses, status.ApplMgmt)
+ writeStatus(mx, "load", componentHealthStatuses, status.Load)
+ writeStatus(mx, "mem", componentHealthStatuses, status.Mem)
+ writeStatus(mx, "swap", componentHealthStatuses, status.Swap)
+ writeStatus(mx, "database_storage", componentHealthStatuses, status.DatabaseStorage)
+ writeStatus(mx, "storage", componentHealthStatuses, status.Storage)
+ writeStatus(mx, "software_packages", softwareHealthStatuses, status.SoftwarePackages)
+
+ return mx, nil
+}
+
+func (vc *VCSA) scrapeHealth(status *vcsaHealthStatus) {
+ wg := &sync.WaitGroup{}
+
+ scrape := func(fn func() (string, error), value **string) {
+ v, err := fn()
+ if err != nil {
+ vc.Error(err)
+ return
+ }
+ *value = &v
+ }
+
+ for _, fn := range []func(){
+ func() { scrape(vc.client.System, &status.System) },
+ func() { scrape(vc.client.ApplMgmt, &status.ApplMgmt) },
+ func() { scrape(vc.client.Load, &status.Load) },
+ func() { scrape(vc.client.DatabaseStorage, &status.DatabaseStorage) },
+ func() { scrape(vc.client.Storage, &status.Storage) },
+ func() { scrape(vc.client.Mem, &status.Mem) },
+ func() { scrape(vc.client.Swap, &status.Swap) },
+ func() { scrape(vc.client.SoftwarePackages, &status.SoftwarePackages) },
+ } {
+ fn := fn
+
+ wg.Add(1)
+ go func() { defer wg.Done(); fn() }()
+ }
+
+ wg.Wait()
+}
+
+func writeStatus(mx map[string]int64, key string, statuses []string, status *string) {
+ if status == nil {
+ return
+ }
+
+ var found bool
+ for _, s := range statuses {
+ mx[key+"_status_"+s] = boolToInt(s == *status)
+ found = found || s == *status
+ }
+ mx[key+"_status_unknown"] = boolToInt(!found)
+}
+
+func boolToInt(v bool) int64 {
+ if v {
+ return 1
+ }
+ return 0
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/config_schema.json b/src/go/collectors/go.d.plugin/modules/vcsa/config_schema.json
new file mode 100644
index 000000000..52a9d90ff
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/config_schema.json
@@ -0,0 +1,168 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "vCenter Server Appliance collector configuration.",
+ "type": "object",
+ "properties": {
+ "update_every": {
+ "title": "Update every",
+ "description": "Data collection interval, measured in seconds.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 1
+ },
+ "url": {
+ "title": "URL",
+ "description": "The base URL of the VCSA server.",
+ "type": "string",
+ "format": "uri"
+ },
+ "timeout": {
+ "title": "Timeout",
+ "description": "The timeout in seconds for the HTTP request.",
+ "type": "number",
+ "minimum": 0.5,
+ "default": 1
+ },
+ "not_follow_redirects": {
+ "title": "Not follow redirects",
+ "description": "If set, the client will not follow HTTP redirects automatically.",
+ "type": "boolean"
+ },
+ "username": {
+ "title": "Username",
+ "description": "The username for basic authentication.",
+ "type": "string",
+ "sensitive": true
+ },
+ "password": {
+ "title": "Password",
+ "description": "The password for basic authentication.",
+ "type": "string",
+ "sensitive": true
+ },
+ "proxy_url": {
+ "title": "Proxy URL",
+ "description": "The URL of the proxy server.",
+ "type": "string"
+ },
+ "proxy_username": {
+ "title": "Proxy username",
+ "description": "The username for proxy authentication.",
+ "type": "string",
+ "sensitive": true
+ },
+ "proxy_password": {
+ "title": "Proxy password",
+ "description": "The password for proxy authentication.",
+ "type": "string",
+ "sensitive": true
+ },
+ "headers": {
+ "title": "Headers",
+ "description": "Additional HTTP headers to include in the request.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "tls_skip_verify": {
+ "title": "Skip TLS verification",
+ "description": "If set, TLS certificate verification will be skipped.",
+ "type": "boolean"
+ },
+ "tls_ca": {
+ "title": "TLS CA",
+ "description": "The path to the CA certificate file for TLS verification.",
+ "type": "string",
+ "pattern": "^$|^/"
+ },
+ "tls_cert": {
+ "title": "TLS certificate",
+ "description": "The path to the client certificate file for TLS authentication.",
+ "type": "string",
+ "pattern": "^$|^/"
+ },
+ "tls_key": {
+ "title": "TLS key",
+ "description": "The path to the client key file for TLS authentication.",
+ "type": "string",
+ "pattern": "^$|^/"
+ }
+ },
+ "required": [
+ "url"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "ui:flavour": "tabs",
+ "ui:options": {
+ "tabs": [
+ {
+ "title": "Base",
+ "fields": [
+ "update_every",
+ "url",
+ "timeout",
+ "not_follow_redirects"
+ ]
+ },
+ {
+ "title": "Auth",
+ "fields": [
+ "username",
+ "password"
+ ]
+ },
+ {
+ "title": "TLS",
+ "fields": [
+ "tls_skip_verify",
+ "tls_ca",
+ "tls_cert",
+ "tls_key"
+ ]
+ },
+ {
+ "title": "Proxy",
+ "fields": [
+ "proxy_url",
+ "proxy_username",
+ "proxy_password"
+ ]
+ },
+ {
+ "title": "Headers",
+ "fields": [
+ "headers"
+ ]
+ }
+ ]
+ },
+ "uiOptions": {
+ "fullPage": true
+ },
+ "url": {
+ "ui:placeholder": "https://203.0.113.0"
+ },
+ "timeout": {
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
+ },
+ "username": {
+ "ui:placeholder": "admin@vsphere.local"
+ },
+ "password": {
+ "ui:widget": "password"
+ },
+ "proxy_password": {
+ "ui:widget": "password"
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/init.go b/src/go/collectors/go.d.plugin/modules/vcsa/init.go
new file mode 100644
index 000000000..112239428
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/init.go
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package vcsa
+
+import (
+ "errors"
+
+ "github.com/netdata/netdata/go/go.d.plugin/modules/vcsa/client"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+)
+
+func (vc *VCSA) validateConfig() error {
+ if vc.URL == "" {
+ return errors.New("URL not set")
+ }
+ if vc.Username == "" || vc.Password == "" {
+ return errors.New("username or password not set")
+ }
+ return nil
+}
+
+func (vc *VCSA) initHealthClient() (*client.Client, error) {
+ httpClient, err := web.NewHTTPClient(vc.Client)
+ if err != nil {
+ return nil, err
+ }
+
+ return client.New(httpClient, vc.URL, vc.Username, vc.Password), nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/integrations/vcenter_server_appliance.md b/src/go/collectors/go.d.plugin/modules/vcsa/integrations/vcenter_server_appliance.md
new file mode 100644
index 000000000..5c56780f6
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/integrations/vcenter_server_appliance.md
@@ -0,0 +1,257 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/vcsa/README.md"
+meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/vcsa/metadata.yaml"
+sidebar_label: "vCenter Server Appliance"
+learn_status: "Published"
+learn_rel_path: "Collecting Metrics/Containers and VMs"
+most_popular: False
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# vCenter Server Appliance
+
+
+<img src="https://netdata.cloud/img/vmware.svg" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: vcsa
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This collector monitors [health statistics](https://developer.vmware.com/apis/vsphere-automation/latest/appliance/health/) of vCenter Server Appliance servers.
+
+
+
+
+This collector is supported on all platforms.
+
+This collector supports collecting metrics from multiple instances of this integration, including remote instances.
+
+
+### Default Behavior
+
+#### Auto-Detection
+
+This integration doesn't support auto-detection.
+
+#### Limits
+
+The default configuration for this integration does not impose any limits on data collection.
+
+#### Performance Impact
+
+The default configuration for this integration is not expected to impose a significant performance impact on the system.
+
+
+## Metrics
+
+Metrics grouped by *scope*.
+
+The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
+
+
+
+### Per vCenter Server Appliance instance
+
+These metrics refer to the entire monitored application.
+<details>
+<summary>See health statuses</summary>
+Overall System Health:
+
+| Status | Description |
+|:-------:|:-------------------------------------------------------------------------------------------------------------------------|
+| green | All components in the appliance are healthy. |
+| yellow | One or more components in the appliance might become overloaded soon. |
+| orange | One or more components in the appliance might be degraded. |
+| red | One or more components in the appliance might be in an unusable status and the appliance might become unresponsive soon. |
+| gray | No health data is available. |
+| unknown | Collector failed to decode status. |
+
+Components Health:
+
+| Status | Description |
+|:-------:|:-------------------------------------------------------------|
+| green | The component is healthy. |
+| yellow | The component is healthy, but may have some problems. |
+| orange | The component is degraded, and may have serious problems. |
+| red | The component is unavailable, or will stop functioning soon. |
+| gray | No health data is available. |
+| unknown | Collector failed to decode status. |
+
+Software Updates Health:
+
+| Status | Description |
+|:-------:|:-----------------------------------------------------|
+| green | No updates available. |
+| orange | Non-security patches might be available. |
+| red | Security patches might be available. |
+| gray | An error retrieving information on software updates. |
+| unknown | Collector failed to decode status. |
+
+</details>
+
+
+This scope has no labels.
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| vcsa.system_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.applmgmt_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.load_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.mem_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.swap_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.database_storage_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.storage_health_status | green, red, yellow, orange, gray, unknown | status |
+| vcsa.software_packages_health_status | green, red, orange, gray, unknown | status |
+
+
+
+## Alerts
+
+
+The following alerts are available:
+
+| Alert name | On metric | Description |
+|:------------|:----------|:------------|
+| [ vcsa_system_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.system_health_status | VCSA overall system status is orange. One or more components are degraded. |
+| [ vcsa_system_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.system_health_status | VCSA overall system status is red. One or more components are unavailable or will stop functioning soon. |
+| [ vcsa_applmgmt_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.applmgmt_health_status | VCSA ApplMgmt component status is orange. It is degraded, and may have serious problems. |
+| [ vcsa_applmgmt_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.applmgmt_health_status | VCSA ApplMgmt component status is red. It is unavailable, or will stop functioning soon. |
+| [ vcsa_load_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.load_health_status | VCSA Load component status is orange. It is degraded, and may have serious problems. |
+| [ vcsa_load_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.load_health_status | VCSA Load component status is red. It is unavailable, or will stop functioning soon. |
+| [ vcsa_mem_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.mem_health_status | VCSA Memory component status is orange. It is degraded, and may have serious problems. |
+| [ vcsa_mem_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.mem_health_status | VCSA Memory component status is red. It is unavailable, or will stop functioning soon. |
+| [ vcsa_swap_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.swap_health_status | VCSA Swap component status is orange. It is degraded, and may have serious problems. |
+| [ vcsa_swap_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.swap_health_status | VCSA Swap component status is red. It is unavailable, or will stop functioning soon. |
+| [ vcsa_database_storage_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.database_storage_health_status | VCSA Database Storage component status is orange. It is degraded, and may have serious problems. |
+| [ vcsa_database_storage_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.database_storage_health_status | VCSA Database Storage component status is red. It is unavailable, or will stop functioning soon. |
+| [ vcsa_storage_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.storage_health_status | VCSA Storage component status is orange. It is degraded, and may have serious problems. |
+| [ vcsa_storage_health_crit ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.storage_health_status | VCSA Storage component status is red. It is unavailable, or will stop functioning soon. |
+| [ vcsa_software_packages_health_warn ](https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf) | vcsa.software_packages_health_status | VCSA software packages security updates are available. |
+
+
+## Setup
+
+### Prerequisites
+
+No action required.
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/vcsa.conf`.
+
+
+You can edit the configuration file using the `edit-config` script from the
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration.md#the-netdata-config-directory).
+
+```bash
+cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
+sudo ./edit-config go.d/vcsa.conf
+```
+#### Options
+
+The following options can be defined globally: update_every, autodetection_retry.
+
+
+<details><summary>Config options</summary>
+
+| Name | Description | Default | Required |
+|:----|:-----------|:-------|:--------:|
+| update_every | Data collection frequency. | 5 | no |
+| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no |
+| url | Server URL. | | yes |
+| timeout | HTTP request timeout. | 1 | no |
+| username | Username for basic HTTP authentication. | | yes |
+| password | Password for basic HTTP authentication. | | yes |
+| proxy_url | Proxy URL. | | no |
+| proxy_username | Username for proxy basic HTTP authentication. | | no |
+| proxy_password | Password for proxy basic HTTP authentication. | | no |
+| method | HTTP request method. | GET | no |
+| body | HTTP request body. | | no |
+| headers | HTTP request headers. | | no |
+| not_follow_redirects | Redirect handling policy. Controls whether the client follows redirects. | false | no |
+| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | false | no |
+| tls_ca | Certification authority that the client uses when verifying the server's certificates. | | no |
+| tls_cert | Client TLS certificate. | | no |
+| tls_key | Client TLS key. | | no |
+
+</details>
+
+#### Examples
+
+##### Basic
+
+An example configuration.
+
+<details><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: vcsa1
+ url: https://203.0.113.1
+ username: admin@vsphere.local
+ password: password
+
+```
+</details>
+
+##### Multi-instance
+
+> **Note**: When you define multiple jobs, their names must be unique.
+
+Two instances.
+
+
+<details><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: vcsa1
+ url: https://203.0.113.1
+ username: admin@vsphere.local
+ password: password
+
+ - name: vcsa2
+ url: https://203.0.113.10
+ username: admin@vsphere.local
+ password: password
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+To troubleshoot issues with the `vcsa` collector, run the `go.d.plugin` with the debug option enabled. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `go.d.plugin` to debug the collector:
+
+ ```bash
+ ./go.d.plugin -d -m vcsa
+ ```
+
+
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/metadata.yaml b/src/go/collectors/go.d.plugin/modules/vcsa/metadata.yaml
new file mode 100644
index 000000000..d619f3d96
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/metadata.yaml
@@ -0,0 +1,346 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-vcsa
+ plugin_name: go.d.plugin
+ module_name: vcsa
+ monitored_instance:
+ name: vCenter Server Appliance
+ link: https://docs.vmware.com/en/VMware-vSphere/6.5/com.vmware.vsphere.vcsa.doc/GUID-223C2821-BD98-4C7A-936B-7DBE96291BA4.html
+ icon_filename: vmware.svg
+ categories:
+ - data-collection.containers-and-vms
+ keywords:
+ - vmware
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: false
+ overview:
+ data_collection:
+ metrics_description: |
+ This collector monitors [health statistics](https://developer.vmware.com/apis/vsphere-automation/latest/appliance/health/) of vCenter Server Appliance servers.
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list: []
+ configuration:
+ file:
+ name: "go.d/vcsa.conf"
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: "5"
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: "0"
+ required: false
+ - name: url
+ description: Server URL.
+ default_value: ""
+ required: true
+ - name: timeout
+ description: HTTP request timeout.
+ default_value: "1"
+ required: false
+ - name: username
+ description: Username for basic HTTP authentication.
+ default_value: ""
+ required: true
+ - name: password
+ description: Password for basic HTTP authentication.
+ default_value: ""
+ required: true
+ - name: proxy_url
+ description: Proxy URL.
+ default_value: ""
+ required: false
+ - name: proxy_username
+ description: Username for proxy basic HTTP authentication.
+ default_value: ""
+ required: false
+ - name: proxy_password
+ description: Password for proxy basic HTTP authentication.
+ default_value: ""
+ required: false
+ - name: method
+ description: HTTP request method.
+ default_value: "GET"
+ required: false
+ - name: body
+ description: HTTP request body.
+ default_value: ""
+ required: false
+ - name: headers
+ description: HTTP request headers.
+ default_value: ""
+ required: false
+ - name: not_follow_redirects
+ description: Redirect handling policy. Controls whether the client follows redirects.
+ default_value: "false"
+ required: false
+ - name: tls_skip_verify
+ description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
+ default_value: "false"
+ required: false
+ - name: tls_ca
+ description: Certification authority that the client uses when verifying the server's certificates.
+ default_value: ""
+ required: false
+ - name: tls_cert
+ description: Client TLS certificate.
+ default_value: ""
+ required: false
+ - name: tls_key
+ description: Client TLS key.
+ default_value: ""
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: Basic
+ description: An example configuration.
+ config: |
+ jobs:
+ - name: vcsa1
+ url: https://203.0.113.1
+ username: admin@vsphere.local
+ password: password
+ - name: Multi-instance
+ description: |
+ > **Note**: When you define multiple jobs, their names must be unique.
+
+ Two instances.
+ config: |
+ jobs:
+ - name: vcsa1
+ url: https://203.0.113.1
+ username: admin@vsphere.local
+ password: password
+
+ - name: vcsa2
+ url: https://203.0.113.10
+ username: admin@vsphere.local
+ password: password
+ troubleshooting:
+ problems:
+ list: []
+ alerts:
+ - name: vcsa_system_health_warn
+ metric: vcsa.system_health_status
+ info: VCSA overall system status is orange. One or more components are degraded.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_system_health_crit
+ metric: vcsa.system_health_status
+ info: VCSA overall system status is red. One or more components are unavailable or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_applmgmt_health_warn
+ metric: vcsa.applmgmt_health_status
+ info: VCSA ApplMgmt component status is orange. It is degraded, and may have serious problems.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_applmgmt_health_crit
+ metric: vcsa.applmgmt_health_status
+ info: VCSA ApplMgmt component status is red. It is unavailable, or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_load_health_warn
+ metric: vcsa.load_health_status
+ info: VCSA Load component status is orange. It is degraded, and may have serious problems.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_load_health_crit
+ metric: vcsa.load_health_status
+ info: VCSA Load component status is red. It is unavailable, or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_mem_health_warn
+ metric: vcsa.mem_health_status
+ info: VCSA Memory component status is orange. It is degraded, and may have serious problems.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_mem_health_crit
+ metric: vcsa.mem_health_status
+ info: VCSA Memory component status is red. It is unavailable, or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_swap_health_warn
+ metric: vcsa.swap_health_status
+ info: VCSA Swap component status is orange. It is degraded, and may have serious problems.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_swap_health_crit
+ metric: vcsa.swap_health_status
+ info: VCSA Swap component status is red. It is unavailable, or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_database_storage_health_warn
+ metric: vcsa.database_storage_health_status
+ info: VCSA Database Storage component status is orange. It is degraded, and may have serious problems.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_database_storage_health_crit
+ metric: vcsa.database_storage_health_status
+ info: VCSA Database Storage component status is red. It is unavailable, or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_storage_health_warn
+ metric: vcsa.storage_health_status
+ info: VCSA Storage component status is orange. It is degraded, and may have serious problems.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_storage_health_crit
+ metric: vcsa.storage_health_status
+ info: VCSA Storage component status is red. It is unavailable, or will stop functioning soon.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ - name: vcsa_software_packages_health_warn
+ metric: vcsa.software_packages_health_status
+ info: VCSA software packages security updates are available.
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/vcsa.conf
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: ""
+ availability: []
+ scopes:
+ - name: global
+ description: |
+ These metrics refer to the entire monitored application.
+ <details>
+ <summary>See health statuses</summary>
+ Overall System Health:
+
+ | Status | Description |
+ |:-------:|:-------------------------------------------------------------------------------------------------------------------------|
+ | green | All components in the appliance are healthy. |
+ | yellow | One or more components in the appliance might become overloaded soon. |
+ | orange | One or more components in the appliance might be degraded. |
+ | red | One or more components in the appliance might be in an unusable status and the appliance might become unresponsive soon. |
+ | gray | No health data is available. |
+ | unknown | Collector failed to decode status. |
+
+ Components Health:
+
+ | Status | Description |
+ |:-------:|:-------------------------------------------------------------|
+ | green | The component is healthy. |
+ | yellow | The component is healthy, but may have some problems. |
+ | orange | The component is degraded, and may have serious problems. |
+ | red | The component is unavailable, or will stop functioning soon. |
+ | gray | No health data is available. |
+ | unknown | Collector failed to decode status. |
+
+ Software Updates Health:
+
+ | Status | Description |
+ |:-------:|:-----------------------------------------------------|
+ | green | No updates available. |
+ | orange | Non-security patches might be available. |
+ | red | Security patches might be available. |
+ | gray | An error retrieving information on software updates. |
+ | unknown | Collector failed to decode status. |
+
+ </details>
+ labels: []
+ metrics:
+ - name: vcsa.system_health_status
+ description: VCSA Overall System health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.applmgmt_health_status
+ description: VCSA ApplMgmt health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.load_health_status
+ description: VCSA Load health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.mem_health_status
+ description: VCSA Memory health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.swap_health_status
+ description: VCSA Swap health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.database_storage_health_status
+ description: VCSA Database Storage health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.storage_health_status
+ description: VCSA Storage health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: yellow
+ - name: orange
+ - name: gray
+ - name: unknown
+ - name: vcsa.software_packages_health_status
+ description: VCSA Software Updates health status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: green
+ - name: red
+ - name: orange
+ - name: gray
+ - name: unknown
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.json b/src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.json
new file mode 100644
index 000000000..984c3ed6e
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.json
@@ -0,0 +1,20 @@
+{
+ "update_every": 123,
+ "url": "ok",
+ "body": "ok",
+ "method": "ok",
+ "headers": {
+ "ok": "ok"
+ },
+ "username": "ok",
+ "password": "ok",
+ "proxy_url": "ok",
+ "proxy_username": "ok",
+ "proxy_password": "ok",
+ "timeout": 123.123,
+ "not_follow_redirects": true,
+ "tls_ca": "ok",
+ "tls_cert": "ok",
+ "tls_key": "ok",
+ "tls_skip_verify": true
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.yaml
new file mode 100644
index 000000000..8558b61cc
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/testdata/config.yaml
@@ -0,0 +1,17 @@
+update_every: 123
+url: "ok"
+body: "ok"
+method: "ok"
+headers:
+ ok: "ok"
+username: "ok"
+password: "ok"
+proxy_url: "ok"
+proxy_username: "ok"
+proxy_password: "ok"
+timeout: 123.123
+not_follow_redirects: yes
+tls_ca: "ok"
+tls_cert: "ok"
+tls_key: "ok"
+tls_skip_verify: yes
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/vcsa.go b/src/go/collectors/go.d.plugin/modules/vcsa/vcsa.go
new file mode 100644
index 000000000..f5e86d082
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/vcsa.go
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package vcsa
+
+import (
+ _ "embed"
+ "errors"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("vcsa", module.Creator{
+ JobConfigSchema: configSchema,
+ Defaults: module.Defaults{
+ UpdateEvery: 5, // VCSA health checks freq is 5 second.
+ },
+ Create: func() module.Module { return New() },
+ })
+}
+
+func New() *VCSA {
+ return &VCSA{
+ Config: Config{
+ HTTP: web.HTTP{
+ Client: web.Client{
+ Timeout: web.Duration(time.Second * 5),
+ },
+ },
+ },
+ charts: vcsaHealthCharts.Copy(),
+ }
+}
+
+type Config struct {
+ web.HTTP `yaml:",inline" json:""`
+ UpdateEvery int `yaml:"update_every" json:"update_every"`
+}
+
+type (
+ VCSA struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ charts *module.Charts
+
+ client healthClient
+ }
+
+ healthClient interface {
+ Login() error
+ Logout() error
+ Ping() error
+ ApplMgmt() (string, error)
+ DatabaseStorage() (string, error)
+ Load() (string, error)
+ Mem() (string, error)
+ SoftwarePackages() (string, error)
+ Storage() (string, error)
+ Swap() (string, error)
+ System() (string, error)
+ }
+)
+
+func (vc *VCSA) Configuration() any {
+ return vc.Config
+}
+
+func (vc *VCSA) Init() error {
+ if err := vc.validateConfig(); err != nil {
+ vc.Error(err)
+ return err
+ }
+
+ c, err := vc.initHealthClient()
+ if err != nil {
+ vc.Errorf("error on creating health client : %vc", err)
+ return err
+ }
+ vc.client = c
+
+ vc.Debugf("using URL %s", vc.URL)
+ vc.Debugf("using timeout: %s", vc.Timeout)
+
+ return nil
+}
+
+func (vc *VCSA) Check() error {
+ err := vc.client.Login()
+ if err != nil {
+ vc.Error(err)
+ return err
+ }
+
+ mx, err := vc.collect()
+ if err != nil {
+ vc.Error(err)
+ return err
+ }
+
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+
+ return nil
+}
+
+func (vc *VCSA) Charts() *module.Charts {
+ return vc.charts
+}
+
+func (vc *VCSA) Collect() map[string]int64 {
+ mx, err := vc.collect()
+ if err != nil {
+ vc.Error(err)
+ }
+
+ if len(mx) == 0 {
+ return nil
+ }
+ return mx
+}
+
+func (vc *VCSA) Cleanup() {
+ if vc.client == nil {
+ return
+ }
+ err := vc.client.Logout()
+ if err != nil {
+ vc.Errorf("error on logout : %v", err)
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vcsa/vcsa_test.go b/src/go/collectors/go.d.plugin/modules/vcsa/vcsa_test.go
new file mode 100644
index 000000000..ccd659665
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vcsa/vcsa_test.go
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package vcsa
+
+import (
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestVCSA_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &VCSA{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestVCSA_Init(t *testing.T) {
+ job := prepareVCSA()
+
+ assert.NoError(t, job.Init())
+ assert.NotNil(t, job.client)
+}
+
+func TestVCenter_InitErrorOnValidatingInitParameters(t *testing.T) {
+ job := New()
+
+ assert.Error(t, job.Init())
+}
+
+func TestVCenter_InitErrorOnCreatingClient(t *testing.T) {
+ job := prepareVCSA()
+ job.Client.TLSConfig.TLSCA = "testdata/tls"
+
+ assert.Error(t, job.Init())
+}
+
+func TestVCenter_Check(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ job.client = &mockVCenterHealthClient{}
+
+ assert.NoError(t, job.Check())
+}
+
+func TestVCenter_CheckErrorOnLogin(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ job.client = &mockVCenterHealthClient{
+ login: func() error { return errors.New("login mock error") },
+ }
+
+ assert.Error(t, job.Check())
+}
+
+func TestVCenter_CheckEnsureLoggedIn(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ mock := &mockVCenterHealthClient{}
+ job.client = mock
+
+ assert.NoError(t, job.Check())
+ assert.True(t, mock.loginCalls == 1)
+}
+
+func TestVCenter_Cleanup(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ mock := &mockVCenterHealthClient{}
+ job.client = mock
+ job.Cleanup()
+
+ assert.True(t, mock.logoutCalls == 1)
+}
+
+func TestVCenter_CleanupWithNilClient(t *testing.T) {
+ job := prepareVCSA()
+
+ assert.NotPanics(t, job.Cleanup)
+}
+
+func TestVCenter_Charts(t *testing.T) {
+ assert.NotNil(t, New().Charts())
+}
+
+func TestVCenter_Collect(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ mock := &mockVCenterHealthClient{}
+ job.client = mock
+
+ expected := map[string]int64{
+ "applmgmt_status_gray": 0,
+ "applmgmt_status_green": 1,
+ "applmgmt_status_orange": 0,
+ "applmgmt_status_red": 0,
+ "applmgmt_status_unknown": 0,
+ "applmgmt_status_yellow": 0,
+ "database_storage_status_gray": 0,
+ "database_storage_status_green": 1,
+ "database_storage_status_orange": 0,
+ "database_storage_status_red": 0,
+ "database_storage_status_unknown": 0,
+ "database_storage_status_yellow": 0,
+ "load_status_gray": 0,
+ "load_status_green": 1,
+ "load_status_orange": 0,
+ "load_status_red": 0,
+ "load_status_unknown": 0,
+ "load_status_yellow": 0,
+ "mem_status_gray": 0,
+ "mem_status_green": 1,
+ "mem_status_orange": 0,
+ "mem_status_red": 0,
+ "mem_status_unknown": 0,
+ "mem_status_yellow": 0,
+ "software_packages_status_gray": 0,
+ "software_packages_status_green": 1,
+ "software_packages_status_orange": 0,
+ "software_packages_status_red": 0,
+ "software_packages_status_unknown": 0,
+ "storage_status_gray": 0,
+ "storage_status_green": 1,
+ "storage_status_orange": 0,
+ "storage_status_red": 0,
+ "storage_status_unknown": 0,
+ "storage_status_yellow": 0,
+ "swap_status_gray": 0,
+ "swap_status_green": 1,
+ "swap_status_orange": 0,
+ "swap_status_red": 0,
+ "swap_status_unknown": 0,
+ "swap_status_yellow": 0,
+ "system_status_gray": 0,
+ "system_status_green": 1,
+ "system_status_orange": 0,
+ "system_status_red": 0,
+ "system_status_unknown": 0,
+ "system_status_yellow": 0,
+ }
+
+ assert.Equal(t, expected, job.Collect())
+}
+
+func TestVCenter_CollectEnsurePingIsCalled(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ mock := &mockVCenterHealthClient{}
+ job.client = mock
+ job.Collect()
+
+ assert.True(t, mock.pingCalls == 1)
+}
+
+func TestVCenter_CollectErrorOnPing(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ mock := &mockVCenterHealthClient{
+ ping: func() error { return errors.New("ping mock error") },
+ }
+ job.client = mock
+
+ assert.Zero(t, job.Collect())
+}
+
+func TestVCenter_CollectErrorOnHealthCalls(t *testing.T) {
+ job := prepareVCSA()
+ require.NoError(t, job.Init())
+ mock := &mockVCenterHealthClient{
+ applMgmt: func() (string, error) { return "", errors.New("applMgmt mock error") },
+ databaseStorage: func() (string, error) { return "", errors.New("databaseStorage mock error") },
+ load: func() (string, error) { return "", errors.New("load mock error") },
+ mem: func() (string, error) { return "", errors.New("mem mock error") },
+ softwarePackages: func() (string, error) { return "", errors.New("softwarePackages mock error") },
+ storage: func() (string, error) { return "", errors.New("storage mock error") },
+ swap: func() (string, error) { return "", errors.New("swap mock error") },
+ system: func() (string, error) { return "", errors.New("system mock error") },
+ }
+ job.client = mock
+
+ assert.Zero(t, job.Collect())
+}
+
+func prepareVCSA() *VCSA {
+ vc := New()
+ vc.URL = "https://127.0.0.1:38001"
+ vc.Username = "user"
+ vc.Password = "pass"
+
+ return vc
+}
+
+type mockVCenterHealthClient struct {
+ login func() error
+ logout func() error
+ ping func() error
+ applMgmt func() (string, error)
+ databaseStorage func() (string, error)
+ load func() (string, error)
+ mem func() (string, error)
+ softwarePackages func() (string, error)
+ storage func() (string, error)
+ swap func() (string, error)
+ system func() (string, error)
+ loginCalls int
+ logoutCalls int
+ pingCalls int
+}
+
+func (m *mockVCenterHealthClient) Login() error {
+ m.loginCalls += 1
+ if m.login == nil {
+ return nil
+ }
+ return m.login()
+}
+
+func (m *mockVCenterHealthClient) Logout() error {
+ m.logoutCalls += 1
+ if m.logout == nil {
+ return nil
+ }
+ return m.logout()
+}
+
+func (m *mockVCenterHealthClient) Ping() error {
+ m.pingCalls += 1
+ if m.ping == nil {
+ return nil
+ }
+ return m.ping()
+}
+
+func (m *mockVCenterHealthClient) ApplMgmt() (string, error) {
+ if m.applMgmt == nil {
+ return "green", nil
+ }
+ return m.applMgmt()
+}
+
+func (m *mockVCenterHealthClient) DatabaseStorage() (string, error) {
+ if m.databaseStorage == nil {
+ return "green", nil
+ }
+ return m.databaseStorage()
+}
+
+func (m *mockVCenterHealthClient) Load() (string, error) {
+ if m.load == nil {
+ return "green", nil
+ }
+ return m.load()
+}
+
+func (m *mockVCenterHealthClient) Mem() (string, error) {
+ if m.mem == nil {
+ return "green", nil
+ }
+ return m.mem()
+}
+
+func (m *mockVCenterHealthClient) SoftwarePackages() (string, error) {
+ if m.softwarePackages == nil {
+ return "green", nil
+ }
+ return m.softwarePackages()
+}
+
+func (m *mockVCenterHealthClient) Storage() (string, error) {
+ if m.storage == nil {
+ return "green", nil
+ }
+ return m.storage()
+}
+
+func (m *mockVCenterHealthClient) Swap() (string, error) {
+ if m.swap == nil {
+ return "green", nil
+ }
+ return m.swap()
+}
+
+func (m *mockVCenterHealthClient) System() (string, error) {
+ if m.system == nil {
+ return "green", nil
+ }
+ return m.system()
+}