Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for custom attributes #5971

Merged
merged 63 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3408955
Initial implementation of Finder
prydin Nov 10, 2018
3468e5e
Refactored to load all properties in one pass
prydin Nov 10, 2018
2b343f2
Fully implemented but not completely tested
prydin Nov 12, 2018
47cca7c
PR candidate 1
prydin Nov 13, 2018
01e4ac9
Removed excessive logging
prydin Nov 13, 2018
3124ad6
Added comments
prydin Nov 14, 2018
db694e6
Scale and performance improvements
prydin Nov 16, 2018
5fdabdb
Use timestamp of latest sample as start point for next round
prydin Nov 18, 2018
6c4cba0
* Improved collection concurrency (one goroutine per object type)
prydin Nov 27, 2018
26e1536
Added hard 100000 metric query limit
prydin Nov 28, 2018
aaa6754
Moved timeout logic to client.go
prydin Nov 30, 2018
e9956ca
Removed WorkerPool and added ThrottledExecutor instead
prydin Dec 4, 2018
94c6fb6
Changed cluster_instances default value to false, since true causes p…
prydin Dec 5, 2018
646c596
Fixed broken test cases
prydin Dec 5, 2018
2de5e4b
Merge remote-tracking branch 'upstream/master' into prydin-scale-impr…
prydin Dec 6, 2018
9ab5b94
Reverted accidental change to wavefront.go
prydin Dec 6, 2018
466b139
Added check for value indices
prydin Dec 7, 2018
bd3fe0d
More robust panic handling
prydin Dec 10, 2018
3ede8cc
Added panic_handler.go
prydin Dec 10, 2018
957762d
Reverted to govmomi 0.18.0
prydin Dec 10, 2018
f563cd8
Exclude tests requiring VPX simulator on 32-bit arch
prydin Dec 11, 2018
5522836
Merged changes from prydin-scale-improvement
prydin Dec 11, 2018
f71b466
Finalized merge from prydin-scalability
prydin Dec 13, 2018
60b4f17
Changed handling of late samples
prydin Dec 19, 2018
3e8c058
Align all timestamps to interval boundary
prydin Dec 19, 2018
6547068
Added documentation for inventory paths
prydin Dec 19, 2018
4442920
Changed logtags from [input.vsphere] to [inputs.vsphere]
prydin Dec 19, 2018
1bb9eae
Fixed broken test case
prydin Dec 19, 2018
dfdd0ee
Fixed 32-bit test issue (bug in vSphere simulator)
prydin Dec 19, 2018
180a7bf
Added cancel-handler to ThrottledExecutor, removed unnecessary warnin…
prydin Dec 21, 2018
0edee16
Merged from prydin-scale-improvement
prydin Dec 21, 2018
a0ca647
Fixed test case issues
prydin Dec 21, 2018
1eb24a3
Back-ported timestamping fixes from pontus-issue-4790
prydin Dec 22, 2018
6819c3a
Fixed test issues
prydin Dec 28, 2018
bc185cd
Merged from upstream
prydin Feb 5, 2019
f1ab53e
Merge branch 'prydin-issue-4790' into prydin-custom-attr
prydin Feb 6, 2019
5ba9296
Send custom attributes as tags
prydin Feb 7, 2019
1083de3
Added custom_properties flag
prydin Feb 9, 2019
b81a9de
Merge branch 'feature-custom-attr' into prydin-custom-attr
prydin Mar 18, 2019
c3f070e
Merge pull request #6 from prydin/prydin-custom-attr
prydin Mar 18, 2019
fce5411
Merge remote-tracking branch 'upstream/master' into feature-custom-attr
prydin Apr 16, 2019
3b7cc61
WIP custom attr
prydin Apr 23, 2019
c535b49
Fixed race condition in Wavefront parser
prydin Apr 24, 2019
6123eb5
Cleaned up the use of sync.Pool
prydin Apr 24, 2019
050e100
Merge branch 'prydin-wavefront-parser' into feature-custom-attr
prydin Apr 24, 2019
82896b4
Added mapping VM NICs to IP addresses
prydin May 28, 2019
c058aff
Fixed datastore name mapping issue
prydin May 28, 2019
9fea30e
Fixed datastore name mapping issue
prydin May 28, 2019
a49fa26
Merge branch 'prydin-issue-5881' into feature-custom-attr
prydin May 28, 2019
af6181a
Merged from upstream
prydin Jun 6, 2019
e6628c4
Added documentation of custom attributes
prydin Jun 7, 2019
e3a1e6e
Added support for vSphere custom attributes.
prydin May 28, 2019
36972df
Added documentation of custom attributes
prydin Jun 7, 2019
ac2af19
Fixed typo in README
prydin Jun 7, 2019
76b6001
Merged from origin
prydin Jun 7, 2019
e48aeb3
Fixed typo in sample config
prydin Jun 7, 2019
cdbcc1c
Removed TestTimeout
prydin Jun 7, 2019
1544282
Fixed format issues
prydin Jun 7, 2019
07b8201
Added guesthostname and cleaned up IP address tag generation
prydin Jul 30, 2019
3d19c3a
Corrected formatting
prydin Jul 30, 2019
ea02316
Fixed more formatting issues
prydin Jul 30, 2019
0811ec0
Fixed formatting
prydin Aug 7, 2019
952ecd7
Reverted changes to wavefront parser
prydin Aug 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion plugins/inputs/vsphere/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,13 @@ vm_metric_exclude = [ "*" ]
"storageAdapter.write.average",
"sys.uptime.latest",
]
## Only consider IPv4 addresses when trying to map NICs to IP addresses.
# ipv4_only = true
danielnelson marked this conversation as resolved.
Show resolved Hide resolved

# host_metric_exclude = [] ## Nothing excluded by default
# host_instances = true ## true by default


## Clusters
# cluster_include = [ "/*/host/**"] # Inventory path to clusters to collect (by default all are collected)
# cluster_metric_include = [] ## if omitted or empty, all metrics are collected
Expand Down Expand Up @@ -173,6 +177,17 @@ vm_metric_exclude = [ "*" ]
## the plugin. Setting this flag to "false" will send values as floats to
## preserve the full precision when averaging takes place.
# use_int_samples = true

## Custom attributes from vCenter can be very useful for queries in order to slice the
## metrics along different dimension and for forming ad-hoc relationships. They are disabled
## by default, since they can add a considerable amount of tags to the resulting metrics. To
## enable, simply set custom_attribute_exlude to [] (empty set) and use custom_attribute_include
## to select the attributes you want to include.
# by default, since they can add a considerable amount of tags to the resulting metrics. To
# enable, simply set custom_attribute_exlude to [] (empty set) and use custom_attribute_include
# to select the attributes you want to include.
# custom_attribute_include = []
# custom_attribute_exclude = ["*"] # Default is to exclude everything

## Optional SSL Config
# ssl_ca = "/path/to/cafile"
Expand Down Expand Up @@ -241,7 +256,7 @@ to a file system. A vSphere inventory has a structure similar to this:
#### Using Inventory Paths
Using familiar UNIX-style paths, one could select e.g. VM2 with the path ```/DC0/vm/VM2```.

Often, we want to select a group of resource, such as all the VMs in a folder. We could use the path ```/DC0/vm/Folder1/*``` for that.
Often, we want to select a group of resource, such as all the VMs in a folder. We could use the path ```/DC0/vm/Folder1/*``` for that.

Another possibility is to select objects using a partial name, such as ```/DC0/vm/Folder1/hadoop*``` yielding all vms in Folder1 with a name starting with "hadoop".

Expand Down
15 changes: 15 additions & 0 deletions plugins/inputs/vsphere/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,18 @@ func (c *Client) ListResources(ctx context.Context, root *view.ContainerView, ki
defer cancel1()
return root.Retrieve(ctx1, kind, ps, dst)
}

func (c *Client) GetCustomFields(ctx context.Context) (map[int32]string, error) {
ctx1, cancel1 := context.WithTimeout(ctx, c.Timeout)
defer cancel1()
cfm := object.NewCustomFieldsManager(c.Client.Client)
fields, err := cfm.Field(ctx1)
if err != nil {
return nil, err
}
r := make(map[int32]string)
for _, f := range fields {
r[f.Key] = f.Name
}
return r, nil
}
152 changes: 128 additions & 24 deletions plugins/inputs/vsphere/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (

var isolateLUN = regexp.MustCompile(".*/([^/]+)/?$")

var isIPv4 = regexp.MustCompile("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")

const metricLookback = 3 // Number of time periods to look back at for non-realtime metrics

const rtMetricLookback = 3 // Number of time periods to look back at for realtime metrics
Expand All @@ -37,16 +39,19 @@ const maxMetadataSamples = 100 // Number of resources to sample for metric metad
// Endpoint is a high-level representation of a connected vCenter endpoint. It is backed by the lower
// level Client type.
type Endpoint struct {
Parent *VSphere
URL *url.URL
resourceKinds map[string]*resourceKind
hwMarks *TSCache
lun2ds map[string]string
discoveryTicker *time.Ticker
collectMux sync.RWMutex
initialized bool
clientFactory *ClientFactory
busy sync.Mutex
Parent *VSphere
URL *url.URL
resourceKinds map[string]*resourceKind
hwMarks *TSCache
lun2ds map[string]string
discoveryTicker *time.Ticker
collectMux sync.RWMutex
initialized bool
clientFactory *ClientFactory
busy sync.Mutex
customFields map[int32]string
customAttrFilter filter.Filter
customAttrEnabled bool
}

type resourceKind struct {
Expand Down Expand Up @@ -80,12 +85,14 @@ type metricEntry struct {
type objectMap map[string]objectRef

type objectRef struct {
name string
altID string
ref types.ManagedObjectReference
parentRef *types.ManagedObjectReference //Pointer because it must be nillable
guest string
dcname string
name string
altID string
ref types.ManagedObjectReference
parentRef *types.ManagedObjectReference //Pointer because it must be nillable
guest string
dcname string
customValues map[string]string
lookup map[string]string
}

func (e *Endpoint) getParent(obj *objectRef, res *resourceKind) (*objectRef, bool) {
Expand All @@ -101,12 +108,14 @@ func (e *Endpoint) getParent(obj *objectRef, res *resourceKind) (*objectRef, boo
// as parameters.
func NewEndpoint(ctx context.Context, parent *VSphere, url *url.URL) (*Endpoint, error) {
e := Endpoint{
URL: url,
Parent: parent,
hwMarks: NewTSCache(1 * time.Hour),
lun2ds: make(map[string]string),
initialized: false,
clientFactory: NewClientFactory(ctx, url, parent),
URL: url,
Parent: parent,
hwMarks: NewTSCache(1 * time.Hour),
lun2ds: make(map[string]string),
initialized: false,
clientFactory: NewClientFactory(ctx, url, parent),
customAttrFilter: newFilterOrPanic(parent.CustomAttributeInclude, parent.CustomAttributeExclude),
customAttrEnabled: anythingEnabled(parent.CustomAttributeExclude),
}

e.resourceKinds = map[string]*resourceKind{
Expand Down Expand Up @@ -259,6 +268,20 @@ func (e *Endpoint) initalDiscovery(ctx context.Context) {
}

func (e *Endpoint) init(ctx context.Context) error {
client, err := e.clientFactory.GetClient(ctx)
if err != nil {
return err
}

// Initial load of custom field metadata
if e.customAttrEnabled {
fields, err := client.GetCustomFields(ctx)
if err != nil {
log.Println("W! [inputs.vsphere] Could not load custom field metadata")
} else {
e.customFields = fields
}
}

if e.Parent.ObjectDiscoveryInterval.Duration > 0 {

Expand Down Expand Up @@ -427,6 +450,16 @@ func (e *Endpoint) discover(ctx context.Context) error {
}
}

// Load custom field metadata
var fields map[int32]string
if e.customAttrEnabled {
fields, err = client.GetCustomFields(ctx)
if err != nil {
log.Println("W! [inputs.vsphere] Could not load custom field metadata")
fields = nil
}
}

// Atomically swap maps
e.collectMux.Lock()
defer e.collectMux.Unlock()
Expand All @@ -436,6 +469,10 @@ func (e *Endpoint) discover(ctx context.Context) error {
}
e.lun2ds = l2d

if fields != nil {
e.customFields = fields
}

sw.Stop()
SendInternalCounterWithTags("discovered_objects", e.URL.Host, map[string]string{"type": "instance-total"}, numRes)
return nil
Expand Down Expand Up @@ -609,14 +646,60 @@ func getVMs(ctx context.Context, e *Endpoint, filter *ResourceFilter) (objectMap
}
guest := "unknown"
uuid := ""

// Collect network information
lookup := make(map[string]string)
for _, net := range r.Guest.Net {
if net.DeviceConfigId == -1 {
continue
}
s := ""
first := true
for _, ip := range net.IpAddress {
if e.Parent.Ipv4Only && !isIPv4.MatchString(ip) {
continue
}
if !first {
s += ","
} else {
first = false
}
s += ip
}
lookup["nic/"+strconv.Itoa(int(net.DeviceConfigId))] = s
}

// Sometimes Config is unknown and returns a nil pointer
//
if r.Config != nil {
guest = cleanGuestID(r.Config.GuestId)
uuid = r.Config.Uuid
}
cvs := make(map[string]string)
if e.customAttrEnabled {
for _, cv := range r.Summary.CustomValue {
val := cv.(*types.CustomFieldStringValue)
if val.Value == "" {
continue
}
key, ok := e.customFields[val.Key]
if !ok {
log.Printf("W! [inputs.vsphere] Metadata for custom field %d not found. Skipping", val.Key)
continue
}
if e.customAttrFilter.Match(key) {
cvs[key] = val.Value
}
}
}
m[r.ExtensibleManagedObject.Reference().Value] = objectRef{
name: r.Name, ref: r.ExtensibleManagedObject.Reference(), parentRef: r.Runtime.Host, guest: guest, altID: uuid}
name: r.Name,
ref: r.ExtensibleManagedObject.Reference(),
parentRef: r.Runtime.Host,
guest: guest,
altID: uuid,
customValues: cvs,
lookup: lookup,
}
}
return m, nil
}
Expand Down Expand Up @@ -1062,6 +1145,18 @@ func (e *Endpoint) populateTags(objectRef *objectRef, resourceType string, resou
t["disk"] = cleanDiskTag(instance)
} else if strings.HasPrefix(name, "net.") {
t["interface"] = instance

// Add IP addresses to NIC data
if resourceType == "vm" && objectRef.lookup != nil {
key := "nic/" + t["interface"]
index := 0
if ip, ok := objectRef.lookup[key]; ok {
for _, s := range strings.Split(ip, ",") {
t["ip"+strconv.Itoa(index)] = s
index++
}
}
}
} else if strings.HasPrefix(name, "storageAdapter.") {
t["adapter"] = instance
} else if strings.HasPrefix(name, "storagePath.") {
Expand All @@ -1076,6 +1171,15 @@ func (e *Endpoint) populateTags(objectRef *objectRef, resourceType string, resou
// default
t["instance"] = v.Instance
}

// Fill in custom values if they exist
if objectRef.customValues != nil {
for k, v := range objectRef.customValues {
if v != "" {
t[k] = v
}
}
}
}

func (e *Endpoint) makeMetricIdentifier(prefix, metric string) (string, string) {
Expand Down
2 changes: 1 addition & 1 deletion plugins/inputs/vsphere/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func init() {

addFields = map[string][]string{
"HostSystem": {"parent"},
"VirtualMachine": {"runtime.host", "config.guestId", "config.uuid", "runtime.powerState"},
"VirtualMachine": {"runtime.host", "config.guestId", "config.uuid", "runtime.powerState", "summary.customValue", "guest.net"},
"Datastore": {"parent", "info"},
"ClusterComputeResource": {"parent"},
"Datacenter": {"parent"},
Expand Down
18 changes: 17 additions & 1 deletion plugins/inputs/vsphere/vsphere.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ type VSphere struct {
DatastoreMetricExclude []string
DatastoreInclude []string
Separator string
CustomAttributeInclude []string
CustomAttributeExclude []string
UseIntSamples bool
Ipv4Only bool

MaxQueryObjects int
MaxQueryMetrics int
Expand Down Expand Up @@ -155,6 +158,8 @@ var sampleConfig = `
"storageAdapter.write.average",
"sys.uptime.latest",
]
## Only consider ipv4 when trying to map IP addresses to interfaces
# ipv4Only = true
# host_metric_exclude = [] ## Nothing excluded by default
# host_instances = true ## true by default

Expand All @@ -173,7 +178,7 @@ var sampleConfig = `
datacenter_metric_exclude = [ "*" ] ## Datacenters are not collected by default.
# datacenter_instances = false ## false by default for Datastores only

## Plugin Settings
## Plugin Settings
## separator character to use for measurement and field names (default: "_")
# separator = "_"

Expand Down Expand Up @@ -208,6 +213,14 @@ var sampleConfig = `
## preserve the full precision when averaging takes place.
# use_int_samples = true

## Custom attributes from vCenter can be very useful for queries in order to slice the
## metrics along different dimension and for forming ad-hoc relationships. They are disabled
## by default, since they can add a considerable amount of tags to the resulting metrics. To
## enable, simply set custom_attribute_exlude to [] (empty set) and use custom_attribute_include
## to select the attributes you want to include.
# custom_attribute_include = []
# custom_attribute_exclude = ["*"]

## Optional SSL Config
# ssl_ca = "/path/to/cafile"
# ssl_cert = "/path/to/certfile"
Expand Down Expand Up @@ -321,7 +334,10 @@ func init() {
DatastoreMetricExclude: nil,
DatastoreInclude: []string{"/*/datastore/**"},
Separator: "_",
CustomAttributeInclude: []string{},
CustomAttributeExclude: []string{"*"},
UseIntSamples: true,
Ipv4Only: true,

MaxQueryObjects: 256,
MaxQueryMetrics: 256,
Expand Down
29 changes: 0 additions & 29 deletions plugins/inputs/vsphere/vsphere_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"regexp"
"sort"
"strings"
"sync"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -256,34 +255,6 @@ func TestThrottledExecutor(t *testing.T) {
require.Equal(t, int64(5), max, "Wrong number of goroutines spawned")
}

func TestTimeout(t *testing.T) {
// Don't run test on 32-bit machines due to bug in simulator.
// https://github.com/vmware/govmomi/issues/1330
var i int
if unsafe.Sizeof(i) < 8 {
return
}

m, s, err := createSim()
if err != nil {
t.Fatal(err)
}
defer m.Remove()
defer s.Close()

v := defaultVSphere()
var acc testutil.Accumulator
v.Vcenters = []string{s.URL.String()}
v.Timeout = internal.Duration{Duration: 1 * time.Nanosecond}
require.NoError(t, v.Start(nil)) // We're not using the Accumulator, so it can be nil.
defer v.Stop()
err = v.Gather(&acc)

// The accumulator must contain exactly one error and it must be a deadline exceeded.
require.Equal(t, 1, len(acc.Errors))
require.True(t, strings.Contains(acc.Errors[0].Error(), "context deadline exceeded"))
}

func TestMaxQuery(t *testing.T) {
// Don't run test on 32-bit machines due to bug in simulator.
// https://github.com/vmware/govmomi/issues/1330
Expand Down
Loading