diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 11:19:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:07:37 +0000 |
commit | b485aab7e71c1625cfc27e0f92c9509f42378458 (patch) | |
tree | ae9abe108601079d1679194de237c9a435ae5b55 /src/go/collectors/go.d.plugin/modules/vcsa | |
parent | Adding upstream version 1.44.3. (diff) | |
download | netdata-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')
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() +} |