summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/vsphere/client
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/collectors/go.d.plugin/modules/vsphere/client/client.go180
-rw-r--r--src/go/collectors/go.d.plugin/modules/vsphere/client/client_test.go175
-rw-r--r--src/go/collectors/go.d.plugin/modules/vsphere/client/keepalive.go45
3 files changed, 400 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/vsphere/client/client.go b/src/go/collectors/go.d.plugin/modules/vsphere/client/client.go
new file mode 100644
index 000000000..827351cf8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vsphere/client/client.go
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package client
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+
+ "github.com/vmware/govmomi"
+ "github.com/vmware/govmomi/performance"
+ "github.com/vmware/govmomi/session"
+ "github.com/vmware/govmomi/view"
+ "github.com/vmware/govmomi/vim25"
+ "github.com/vmware/govmomi/vim25/mo"
+ "github.com/vmware/govmomi/vim25/soap"
+ "github.com/vmware/govmomi/vim25/types"
+)
+
+const (
+ datacenter = "Datacenter"
+ folder = "Folder"
+ computeResource = "ComputeResource"
+ hostSystem = "HostSystem"
+ virtualMachine = "VirtualMachine"
+
+ maxIdleConnections = 32
+)
+
+type Config struct {
+ URL string
+ User string
+ Password string
+ tlscfg.TLSConfig
+ Timeout time.Duration
+}
+
+type Client struct {
+ client *govmomi.Client
+ root *view.ContainerView
+ perf *performance.Manager
+}
+
+func newSoapClient(config Config) (*soap.Client, error) {
+ soapURL, err := soap.ParseURL(config.URL)
+ if err != nil || soapURL == nil {
+ return nil, err
+ }
+ soapURL.User = url.UserPassword(config.User, config.Password)
+ soapClient := soap.NewClient(soapURL, config.TLSConfig.InsecureSkipVerify)
+
+ tlsConfig, err := tlscfg.NewTLSConfig(config.TLSConfig)
+ if err != nil {
+ return nil, err
+ }
+ if tlsConfig != nil && len(tlsConfig.Certificates) > 0 {
+ soapClient.SetCertificate(tlsConfig.Certificates[0])
+ }
+ if config.TLSConfig.TLSCA != "" {
+ if err := soapClient.SetRootCAs(config.TLSConfig.TLSCA); err != nil {
+ return nil, err
+ }
+ }
+
+ if t, ok := soapClient.Transport.(*http.Transport); ok {
+ t.MaxIdleConnsPerHost = maxIdleConnections
+ t.TLSHandshakeTimeout = config.Timeout
+ }
+ soapClient.Timeout = config.Timeout
+
+ return soapClient, nil
+}
+
+func newContainerView(ctx context.Context, client *govmomi.Client) (*view.ContainerView, error) {
+ viewManager := view.NewManager(client.Client)
+ return viewManager.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{}, true)
+}
+
+func newPerformanceManager(client *vim25.Client) *performance.Manager {
+ perfManager := performance.NewManager(client)
+ perfManager.Sort = true
+ return perfManager
+}
+
+func New(config Config) (*Client, error) {
+ ctx := context.Background()
+ soapClient, err := newSoapClient(config)
+ if err != nil {
+ return nil, err
+ }
+
+ vimClient, err := vim25.NewClient(ctx, soapClient)
+ if err != nil {
+ return nil, err
+ }
+
+ vmomiClient := &govmomi.Client{
+ Client: vimClient,
+ SessionManager: session.NewManager(vimClient),
+ }
+
+ userInfo := url.UserPassword(config.User, config.Password)
+ addKeepAlive(vmomiClient, userInfo)
+
+ err = vmomiClient.Login(ctx, userInfo)
+ if err != nil {
+ return nil, err
+ }
+
+ containerView, err := newContainerView(ctx, vmomiClient)
+ if err != nil {
+ return nil, err
+ }
+
+ perfManager := newPerformanceManager(vimClient)
+
+ client := &Client{
+ client: vmomiClient,
+ perf: perfManager,
+ root: containerView,
+ }
+
+ return client, nil
+}
+
+func (c *Client) IsSessionActive() (bool, error) {
+ return c.client.SessionManager.SessionIsActive(context.Background())
+}
+
+func (c *Client) Version() string {
+ return c.client.ServiceContent.About.Version
+}
+
+func (c *Client) Login(userinfo *url.Userinfo) error {
+ return c.client.Login(context.Background(), userinfo)
+}
+
+func (c *Client) Logout() error {
+ return c.client.Logout(context.Background())
+}
+
+func (c *Client) PerformanceMetrics(pqs []types.PerfQuerySpec) ([]performance.EntityMetric, error) {
+ metrics, err := c.perf.Query(context.Background(), pqs)
+ if err != nil {
+ return nil, err
+ }
+ return c.perf.ToMetricSeries(context.Background(), metrics)
+}
+
+func (c *Client) Datacenters(pathSet ...string) (dcs []mo.Datacenter, err error) {
+ err = c.root.Retrieve(context.Background(), []string{datacenter}, pathSet, &dcs)
+ return
+}
+
+func (c *Client) Folders(pathSet ...string) (folders []mo.Folder, err error) {
+ err = c.root.Retrieve(context.Background(), []string{folder}, pathSet, &folders)
+ return
+}
+
+func (c *Client) ComputeResources(pathSet ...string) (computes []mo.ComputeResource, err error) {
+ err = c.root.Retrieve(context.Background(), []string{computeResource}, pathSet, &computes)
+ return
+}
+
+func (c *Client) Hosts(pathSet ...string) (hosts []mo.HostSystem, err error) {
+ err = c.root.Retrieve(context.Background(), []string{hostSystem}, pathSet, &hosts)
+ return
+}
+
+func (c *Client) VirtualMachines(pathSet ...string) (vms []mo.VirtualMachine, err error) {
+ err = c.root.Retrieve(context.Background(), []string{virtualMachine}, pathSet, &vms)
+ return
+}
+
+func (c *Client) CounterInfoByName() (map[string]*types.PerfCounterInfo, error) {
+ return c.perf.CounterInfoByName(context.Background())
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vsphere/client/client_test.go b/src/go/collectors/go.d.plugin/modules/vsphere/client/client_test.go
new file mode 100644
index 000000000..163829f41
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vsphere/client/client_test.go
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package client
+
+import (
+ "crypto/tls"
+ "net/url"
+ "testing"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/vmware/govmomi/simulator"
+ "github.com/vmware/govmomi/vim25/mo"
+ "github.com/vmware/govmomi/vim25/types"
+)
+
+func TestNew(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ v, err := client.IsSessionActive()
+ assert.NoError(t, err)
+ assert.True(t, v)
+}
+
+func TestClient_Version(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ assert.NotEmpty(t, client.Version())
+}
+
+func TestClient_CounterInfoByName(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ v, err := client.CounterInfoByName()
+ assert.NoError(t, err)
+ assert.IsType(t, map[string]*types.PerfCounterInfo{}, v)
+ assert.NotEmpty(t, v)
+}
+
+func TestClient_IsSessionActive(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ v, err := client.IsSessionActive()
+ assert.NoError(t, err)
+ assert.True(t, v)
+}
+
+func TestClient_Login(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ assert.NoError(t, client.Logout())
+
+ err := client.Login(url.UserPassword("admin", "password"))
+ assert.NoError(t, err)
+
+ ok, err := client.IsSessionActive()
+ assert.NoError(t, err)
+ assert.True(t, ok)
+}
+
+func TestClient_Logout(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ assert.NoError(t, client.Logout())
+
+ v, err := client.IsSessionActive()
+ assert.NoError(t, err)
+ assert.False(t, v)
+}
+
+func TestClient_Datacenters(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ dcs, err := client.Datacenters()
+ assert.NoError(t, err)
+ assert.NotEmpty(t, dcs)
+}
+
+func TestClient_Folders(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ folders, err := client.Folders()
+ assert.NoError(t, err)
+ assert.NotEmpty(t, folders)
+}
+
+func TestClient_ComputeResources(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ computes, err := client.ComputeResources()
+ assert.NoError(t, err)
+ assert.NotEmpty(t, computes)
+}
+
+func TestClient_Hosts(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ hosts, err := client.Hosts()
+ assert.NoError(t, err)
+ assert.NotEmpty(t, hosts)
+}
+
+func TestClient_VirtualMachines(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ vms, err := client.VirtualMachines()
+ assert.NoError(t, err)
+ assert.NotEmpty(t, vms)
+}
+
+func TestClient_PerformanceMetrics(t *testing.T) {
+ client, teardown := prepareClient(t)
+ defer teardown()
+
+ hosts, err := client.Hosts()
+ require.NoError(t, err)
+ metrics, err := client.PerformanceMetrics(hostsPerfQuerySpecs(hosts))
+ require.NoError(t, err)
+ assert.True(t, len(metrics) > 0)
+}
+
+func prepareClient(t *testing.T) (client *Client, teardown func()) {
+ model, srv := createSim(t)
+ teardown = func() { model.Remove(); srv.Close() }
+ return newClient(t, srv.URL), teardown
+}
+
+func newClient(t *testing.T, vCenterURL *url.URL) *Client {
+ client, err := New(Config{
+ URL: vCenterURL.String(),
+ User: "admin",
+ Password: "password",
+ Timeout: time.Second * 3,
+ TLSConfig: tlscfg.TLSConfig{InsecureSkipVerify: true},
+ })
+ require.NoError(t, err)
+ return client
+}
+
+func createSim(t *testing.T) (*simulator.Model, *simulator.Server) {
+ model := simulator.VPX()
+ err := model.Create()
+ require.NoError(t, err)
+ model.Service.TLS = new(tls.Config)
+ return model, model.Service.NewServer()
+}
+
+func hostsPerfQuerySpecs(hosts []mo.HostSystem) []types.PerfQuerySpec {
+ var pqs []types.PerfQuerySpec
+ for _, host := range hosts {
+ pq := types.PerfQuerySpec{
+ Entity: host.Reference(),
+ MaxSample: 1,
+ MetricId: []types.PerfMetricId{{CounterId: 32, Instance: ""}},
+ IntervalId: 20,
+ Format: "normal",
+ }
+ pqs = append(pqs, pq)
+ }
+ return pqs
+}
diff --git a/src/go/collectors/go.d.plugin/modules/vsphere/client/keepalive.go b/src/go/collectors/go.d.plugin/modules/vsphere/client/keepalive.go
new file mode 100644
index 000000000..0ce1ef5c0
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/vsphere/client/keepalive.go
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package client
+
+import (
+ "context"
+ "net/url"
+ "time"
+
+ "github.com/vmware/govmomi"
+ "github.com/vmware/govmomi/session"
+ "github.com/vmware/govmomi/vim25/methods"
+ "github.com/vmware/govmomi/vim25/soap"
+ "github.com/vmware/govmomi/vim25/types"
+)
+
+const (
+ keepAliveEvery = time.Second * 15
+)
+
+// TODO: survive vCenter reboot, it looks like we need to re New()
+func addKeepAlive(client *govmomi.Client, userinfo *url.Userinfo) {
+ f := func(rt soap.RoundTripper) error {
+ _, err := methods.GetCurrentTime(context.Background(), rt)
+ if err == nil {
+ return nil
+ }
+
+ if !isNotAuthenticated(err) {
+ return nil
+ }
+
+ _ = client.Login(context.Background(), userinfo)
+ return nil
+ }
+ client.Client.RoundTripper = session.KeepAliveHandler(client.Client.RoundTripper, keepAliveEvery, f)
+}
+
+func isNotAuthenticated(err error) bool {
+ if !soap.IsSoapFault(err) {
+ return false
+ }
+ _, ok := soap.ToSoapFault(err).VimFault().(*types.NotAuthenticated)
+ return ok
+}