From 8d8cc63d0a8c213d83d84100d20ab0d25b2db9f6 Mon Sep 17 00:00:00 2001 From: Sarah Witt Date: Fri, 21 Jul 2023 15:47:07 -0400 Subject: [PATCH] Add support for VM property metrics (#14787) * Add base VM property metrics * Add network ip * Add new properties * Report only VM values and use host tags * Clean up code * Add more tests and add to cache * Add more tests * use vm hostname * Clean up code and add tests for all property metrics * Add submit_property_metric function * Add type hints and add helper methods * Add new metrics and update metadata.csv * Only log if in debug mode * Add unneeded types and log * Remove duplicated code, fix metadata.csv * Move mocks to common file * Sync models * update metadata and docs to reflect interval * Further refactor, standardize metric types, and add another test * Add tests for logging * Add cache time test * Add tools version status metric * Address review comments * use new version of models --- vsphere/assets/configuration/spec.yaml | 12 + vsphere/datadog_checks/vsphere/api.py | 21 +- vsphere/datadog_checks/vsphere/cache.py | 6 + vsphere/datadog_checks/vsphere/config.py | 1 + .../vsphere/config_models/defaults.py | 4 + .../vsphere/config_models/instance.py | 1 + vsphere/datadog_checks/vsphere/constants.py | 30 ++ .../vsphere/data/conf.yaml.example | 11 + vsphere/datadog_checks/vsphere/types.py | 17 +- vsphere/datadog_checks/vsphere/utils.py | 9 +- vsphere/datadog_checks/vsphere/vsphere.py | 260 ++++++++++- vsphere/metadata.csv | 21 +- vsphere/tests/common.py | 356 ++++++++++++++ vsphere/tests/conftest.py | 12 + vsphere/tests/test_check.py | 1 + vsphere/tests/test_unit.py | 438 ++++++++++++++++++ 16 files changed, 1191 insertions(+), 9 deletions(-) diff --git a/vsphere/assets/configuration/spec.yaml b/vsphere/assets/configuration/spec.yaml index 425b5bdb9df37..51537d11928a8 100644 --- a/vsphere/assets/configuration/spec.yaml +++ b/vsphere/assets/configuration/spec.yaml @@ -310,6 +310,18 @@ files: value: type: boolean example: false + - name: collect_property_metrics + description: | + If true, the vSphere integration will collect additional metrics about the resource types + you have configured to monitor. These are configuration properties such as disk information + in a VM and DRS config status in clusters. + + Note: These metrics are collected at a frequency of `refresh_infrastructure_cache_interval`. + Warning: Depending on the size of the vSphere environment, property metric collection can be slow, + very CPU intensive and put pressure on the vCenter Server. + value: + type: boolean + example: false - name: attributes_prefix description: | Custom attributes attached to vSphere resources will be prefixed with this prefix when collected. diff --git a/vsphere/datadog_checks/vsphere/api.py b/vsphere/datadog_checks/vsphere/api.py index a6d7b329c2270..cffe3ad39d63e 100644 --- a/vsphere/datadog_checks/vsphere/api.py +++ b/vsphere/datadog_checks/vsphere/api.py @@ -12,7 +12,12 @@ from datadog_checks.base.log import CheckLoggingAdapter # noqa: F401 from datadog_checks.vsphere.config import VSphereConfig # noqa: F401 -from datadog_checks.vsphere.constants import ALL_RESOURCES, MAX_QUERY_METRICS_OPTION, UNLIMITED_HIST_METRICS_PER_QUERY +from datadog_checks.vsphere.constants import ( + ALL_RESOURCES, + MAX_QUERY_METRICS_OPTION, + UNLIMITED_HIST_METRICS_PER_QUERY, + VM_PROPERTIES, +) from datadog_checks.vsphere.event import ALLOWED_EVENTS from datadog_checks.vsphere.types import InfrastructureData @@ -184,6 +189,10 @@ def _get_raw_infrastructure(self): property_spec.pathSet.append("runtime.powerState") property_spec.pathSet.append("runtime.host") property_spec.pathSet.append("guest.hostName") + if self.config.collect_property_metrics: + for vm_property in VM_PROPERTIES: + property_spec.pathSet.append(vm_property) + property_specs.append(property_spec) # Specify the attribute of the root object to traverse to obtain all the attributes @@ -267,13 +276,21 @@ def get_infrastructure(self): root_folder = self._conn.content.rootFolder infrastructure_data[root_folder] = {"name": root_folder.name, "parent": None} - if self.config.should_collect_attributes: + if self.config.should_collect_attributes or self.config.collect_property_metrics: # Clean up attributes in infrastructure_data, # at this point they are custom pyvmomi objects and the attribute keys are not resolved. attribute_keys = {x.key: x.name for x in self._fetch_all_attributes()} for props in itervalues(infrastructure_data): mor_attributes = [] + if self.config.collect_property_metrics: + all_properties = {} + for attribute_name in VM_PROPERTIES: + attribute_val = props.pop(attribute_name, None) + if attribute_val is not None: + all_properties[attribute_name] = attribute_val + props['properties'] = all_properties + if 'customValue' not in props: continue for attribute in props.pop('customValue'): diff --git a/vsphere/datadog_checks/vsphere/cache.py b/vsphere/datadog_checks/vsphere/cache.py index cecc5d1bca7d8..1e2e6ec2741a7 100644 --- a/vsphere/datadog_checks/vsphere/cache.py +++ b/vsphere/datadog_checks/vsphere/cache.py @@ -148,3 +148,9 @@ def set_mor_props(self, mor, mor_data): if mor_type not in self._mors: self._mors[mor_type] = {} self._mors[mor_type][mor] = mor_data + + def clear_properties(self): + # type: () -> None + for _, mors in self._mors.items(): + for _, mor_props in mors.items(): + mor_props.pop('properties', None) diff --git a/vsphere/datadog_checks/vsphere/config.py b/vsphere/datadog_checks/vsphere/config.py index 22966dea86c9a..feb868440ebde 100644 --- a/vsphere/datadog_checks/vsphere/config.py +++ b/vsphere/datadog_checks/vsphere/config.py @@ -79,6 +79,7 @@ def __init__(self, instance, init_config, log): self.should_collect_tags = is_affirmative(instance.get("collect_tags", False)) self.tags_prefix = instance.get("tags_prefix", DEFAULT_VSPHERE_TAG_PREFIX) self.should_collect_attributes = is_affirmative(instance.get("collect_attributes", False)) + self.collect_property_metrics = is_affirmative(instance.get("collect_property_metrics", False)) self.attr_prefix = instance.get("attributes_prefix", DEFAULT_VSPHERE_ATTR_PREFIX) self.excluded_host_tags = instance.get("excluded_host_tags", []) self.base_tags = instance.get("tags", []) + ["vcenter_server:{}".format(self.hostname)] diff --git a/vsphere/datadog_checks/vsphere/config_models/defaults.py b/vsphere/datadog_checks/vsphere/config_models/defaults.py index 56124b1f62db3..89315b6b76a5c 100644 --- a/vsphere/datadog_checks/vsphere/config_models/defaults.py +++ b/vsphere/datadog_checks/vsphere/config_models/defaults.py @@ -28,6 +28,10 @@ def instance_collect_events_only(): return False +def instance_collect_property_metrics(): + return False + + def instance_collect_tags(): return False diff --git a/vsphere/datadog_checks/vsphere/config_models/instance.py b/vsphere/datadog_checks/vsphere/config_models/instance.py index 0dd11a1255934..3a819f3f9fc33 100644 --- a/vsphere/datadog_checks/vsphere/config_models/instance.py +++ b/vsphere/datadog_checks/vsphere/config_models/instance.py @@ -127,6 +127,7 @@ class InstanceConfig(BaseModel): collect_events: Optional[bool] = None collect_events_only: Optional[bool] = None collect_per_instance_filters: Optional[CollectPerInstanceFilters] = None + collect_property_metrics: Optional[bool] = None collect_tags: Optional[bool] = None collection_level: Optional[int] = None collection_type: Optional[str] = None diff --git a/vsphere/datadog_checks/vsphere/constants.py b/vsphere/datadog_checks/vsphere/constants.py index 19345913e7f88..bfab73f8dd9fc 100644 --- a/vsphere/datadog_checks/vsphere/constants.py +++ b/vsphere/datadog_checks/vsphere/constants.py @@ -57,3 +57,33 @@ DEFAULT_VSPHERE_TAG_PREFIX = "" DEFAULT_VSPHERE_ATTR_PREFIX = "" + +PROPERTY_COUNT_METRICS = [ + "guest.net", + "guest.ipStack.ipRoute", + "guest.net.ipConfig.address", + "guest.toolsRunningStatus", + "guest.toolsVersionStatus2", + "guest.toolsVersion", + "guest.guestFullName", +] +VM_OBJECT_PROPERTIES = ["guest.disk", "guest.net", "guest.ipStack"] + +VM_SIMPLE_PROPERTIES = [ + "guest.toolsRunningStatus", + "guest.toolsVersionStatus2", + "guest.toolsVersion", + "config.hardware.numCoresPerSocket", + "config.cpuAllocation.limit", + "config.cpuAllocation.overheadLimit", + "config.memoryAllocation.limit", + "config.memoryAllocation.overheadLimit", + "summary.config.numCpu", + "summary.config.memorySizeMB", + "summary.config.numEthernetCards", + "summary.config.numVirtualDisks", + "summary.quickStats.uptimeSeconds", + "guest.guestFullName", +] + +VM_PROPERTIES = VM_OBJECT_PROPERTIES + VM_SIMPLE_PROPERTIES diff --git a/vsphere/datadog_checks/vsphere/data/conf.yaml.example b/vsphere/datadog_checks/vsphere/data/conf.yaml.example index 74fb3129fe157..6b4b75ebf9872 100644 --- a/vsphere/datadog_checks/vsphere/data/conf.yaml.example +++ b/vsphere/datadog_checks/vsphere/data/conf.yaml.example @@ -279,6 +279,17 @@ instances: # # collect_attributes: false + ## @param collect_property_metrics - boolean - optional - default: false + ## If true, the vSphere integration will collect additional metrics about the resource types + ## you have configured to monitor. These are configuration properties such as disk information + ## in a VM and DRS config status in clusters. + ## + ## Note: These metrics are collected at a frequency of `refresh_infrastructure_cache_interval`. + ## Warning: Depending on the size of the vSphere environment, property metric collection can be slow, + ## very CPU intensive and put pressure on the vCenter Server. + # + # collect_property_metrics: false + ## @param attributes_prefix - string - optional ## Custom attributes attached to vSphere resources will be prefixed with this prefix when collected. ## Example use cases: diff --git a/vsphere/datadog_checks/vsphere/types.py b/vsphere/datadog_checks/vsphere/types.py index e1211a677c570..d2945bcc2da2d 100644 --- a/vsphere/datadog_checks/vsphere/types.py +++ b/vsphere/datadog_checks/vsphere/types.py @@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional, Pattern, Type, TypedDict # CONFIG ALIASES -from pyVmomi import vim +from pyVmomi import VmomiSupport, vim ResourceFilterConfig = TypedDict( 'ResourceFilterConfig', {'resource': str, 'property': str, 'type': str, 'patterns': List[str]} @@ -52,6 +52,8 @@ MetricName = str CounterId = int +VmomiObject = VmomiSupport.Object + InfrastructureDataItem = TypedDict( 'InfrastructureDataItem', { @@ -59,6 +61,19 @@ 'runtime.host': vim.ManagedEntity, 'guest.hostName': str, 'runtime.powerState': str, + 'summary.config.numCpu': int, + 'summary.config.memorySizeMB': int, + 'summary.config.numEthernetCards': int, + 'summary.config.numVirtualDisks': int, + 'summary.quickStats.uptimeSeconds': int, + 'guest.guestFullName': str, + 'guest.disk': List[VmomiObject], + 'guest.net': List[VmomiObject], + 'guest.ipStack': List[VmomiObject], + 'guest.toolsRunningStatus': str, + 'guest.toolsVersionStatus2': str, + 'guest.toolsVersion': str, + 'config.hardware.numCoresPerSocket': str, 'parent': Optional[vim.ManagedEntity], 'attributes': List[str], }, diff --git a/vsphere/datadog_checks/vsphere/utils.py b/vsphere/datadog_checks/vsphere/utils.py index 235dbe6c09c24..27abda04da92f 100644 --- a/vsphere/datadog_checks/vsphere/utils.py +++ b/vsphere/datadog_checks/vsphere/utils.py @@ -1,7 +1,7 @@ # (C) Datadog, Inc. 2019-present # All rights reserved # Licensed under Simplified BSD License (see LICENSE) -from typing import List, Optional, Type # noqa: F401 +from typing import Any, Dict, List, Optional, Type # noqa: F401 from pyVmomi import vim from six import iteritems @@ -153,3 +153,10 @@ def get_mapped_instance_tag(metric_name): if metric_name.startswith(prefix): return tag_key return 'instance' + + +def add_additional_tags(tags, additional_tags): + # type: (List[str], Dict[str, Optional[Any]]) -> List[str] + for tag_name, tag_value in additional_tags.items(): + if tag_value is not None: + tags.append("{}:{}".format(tag_name, tag_value)) diff --git a/vsphere/datadog_checks/vsphere/vsphere.py b/vsphere/datadog_checks/vsphere/vsphere.py index b1e72b7e76469..0ceeddc303f81 100644 --- a/vsphere/datadog_checks/vsphere/vsphere.py +++ b/vsphere/datadog_checks/vsphere/vsphere.py @@ -8,7 +8,7 @@ from collections import defaultdict from concurrent.futures import as_completed from concurrent.futures.thread import ThreadPoolExecutor -from typing import Any, Dict, Generator, Iterable, List, Set, Type, cast # noqa: F401 +from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Type, cast # noqa: F401 from pyVmomi import vim, vmodl from six import iteritems @@ -24,8 +24,10 @@ DEFAULT_MAX_QUERY_METRICS, HISTORICAL_RESOURCES, MAX_QUERY_METRICS_OPTION, + PROPERTY_COUNT_METRICS, REALTIME_METRICS_INTERVAL_ID, REALTIME_RESOURCES, + VM_SIMPLE_PROPERTIES, ) from datadog_checks.vsphere.event import VSphereEvent from datadog_checks.vsphere.metrics import ALLOWED_METRICS_FOR_MOR, PERCENT_METRICS @@ -38,9 +40,11 @@ MetricName, # noqa: F401 MorBatch, # noqa: F401 ResourceTags, # noqa: F401 + VmomiObject, # noqa: F401 ) from datadog_checks.vsphere.utils import ( MOR_TYPE_AS_STRING, + add_additional_tags, format_metric_name, get_mapped_instance_tag, get_tags_recursively, @@ -207,10 +211,11 @@ def refresh_infrastructure_cache(self): self.log.debug("Refreshing the infrastructure cache...") t0 = Timer() infrastructure_data = self.api.get_infrastructure() + collect_property_metrics = self._config.collect_property_metrics self.gauge( "datadog.vsphere.refresh_infrastructure_cache.time", t0.total(), - tags=self._config.base_tags, + tags=self._config.base_tags + ['collect_property_metrics:{}'.format(collect_property_metrics)], raw=True, hostname=self._hostname, ) @@ -231,6 +236,7 @@ def refresh_infrastructure_cache(self): mor_type_str = MOR_TYPE_AS_STRING[type(mor)] hostname = None tags = [] + mor_payload = {} # type: Dict[str, Any] if isinstance(mor, vim.VirtualMachine): power_state = properties.get("runtime.powerState") @@ -252,6 +258,10 @@ def refresh_infrastructure_cache(self): runtime_hostname = to_string(runtime_host_props.get("name", "unknown")) tags.append('vsphere_host:{}'.format(runtime_hostname)) + if self._config.collect_property_metrics: + all_properties = properties.get('properties', {}) + mor_payload['properties'] = all_properties + if self._config.use_guest_hostname: hostname = properties.get("guest.hostName", mor_name) else: @@ -316,7 +326,7 @@ def refresh_infrastructure_cache(self): hostname, ) - mor_payload = {"tags": tags} # type: Dict[str, Any] + mor_payload["tags"] = tags # type: Dict[str, Any] if hostname: mor_payload['hostname'] = hostname @@ -635,6 +645,236 @@ def collect_events(self): # OR something bad happened (which might happen again indefinitely). self.latest_event_query = collect_start_time + def submit_property_metric( + self, + metric_name, # type: str + metric_value, # type: Any + base_tags, # type: List[str] + hostname, # type: str + resource_metric_suffix, # type: str + additional_tags=None, # type: Optional[Dict[str, Optional[Any]]] + ): + # type: (...) -> None + """ + Submits a property metric: + - If the metric is a count metric (expecting tag data) + 1. Check if should have any tags added (metric value) + 2. Add the tag + 3. If there are still no valuable tags/data, then discard the metric + 4. Submit the metric as a count + - If the metric is a guage + 1. Convert value to a float + 2. Discard if there is no float data + + Then combine all tags and submit the metric. + """ + metric_full_name = "{}.{}".format(resource_metric_suffix, metric_name) + is_count_metric = metric_name in PROPERTY_COUNT_METRICS + + if additional_tags is None: + additional_tags = {} + + if is_count_metric: + no_additional_tags = all(tag is None for tag in additional_tags.values()) + if no_additional_tags: + if metric_value is None: + self.log.debug( + "Could not sumbit property metric- no metric data: name=`%s`, value=`%s`, hostname=`%s`, " + "base tags=`%s` additional tags=`%s`", + metric_full_name, + metric_value, + hostname, + base_tags, + additional_tags, + ) + return + + _, _, tag_name = metric_name.rpartition('.') + property_tag = {tag_name: metric_value} + additional_tags.update(property_tag) + + # set metric value to 1 since it is not a float + metric_value = 1 + + else: + try: + metric_value = float(metric_value) + except Exception: + self.log.debug( + "Could not sumbit property metric- unexpected metric value: name=`%s`, value=`%s`, hostname=`%s`, " + "base tags=`%s` additional tags=`%s`", + metric_full_name, + metric_value, + hostname, + base_tags, + additional_tags, + ) + return + + tags = [] # type: List[str] + tags = tags + base_tags + + add_additional_tags(tags, additional_tags) + + # Use isEnabledFor to avoid unnecessary processing + if self.log.isEnabledFor(logging.DEBUG): + self.log.debug( + "Submit property metric: name=`%s`, value=`%s`, hostname=`%s`, tags=`%s`, count=`%s`", + metric_full_name, + metric_value, + hostname, + tags, + is_count_metric, + ) + metric_method = self.count if is_count_metric else self.gauge + metric_method(metric_full_name, metric_value, tags=tags, hostname=hostname) + + def submit_disk_property_metrics( + self, + disks, # type: List[VmomiObject] + base_tags, # type: List[str] + hostname, # type: str + resource_metric_suffix, # type: str + ): + # type: (...) -> None + for disk in disks: + disk_path = disk.diskPath + file_system_type = disk.filesystemType + free_space = disk.freeSpace + capacity = disk.capacity + disk_tags = {'disk_path': disk_path, 'file_system_type': file_system_type} + + self.submit_property_metric( + 'guest.disk.freeSpace', + free_space, + base_tags, + hostname, + resource_metric_suffix, + additional_tags=disk_tags, + ) + + self.submit_property_metric( + 'guest.disk.capacity', + capacity, + base_tags, + hostname, + resource_metric_suffix, + additional_tags=disk_tags, + ) + + def submit_nic_property_metrics( + self, + nics, # type: List[VmomiObject] + base_tags, # type: List[str] + hostname, # type: str + resource_metric_suffix, # type: str + ): + # type: (...) -> None + for nic in nics: + device_id = nic.deviceConfigId + is_connected = nic.connected + mac_address = nic.macAddress + nic_tags = {'device_id': device_id, 'is_connected': is_connected, 'nic_mac_address': mac_address} + self.submit_property_metric( + 'guest.net', 1, base_tags, hostname, resource_metric_suffix, additional_tags=nic_tags + ) + ip_addresses = nic.ipConfig.ipAddress + for ip_address in ip_addresses: + nic_tags['nic_ip_address'] = ip_address.ipAddress + self.submit_property_metric( + 'guest.net.ipConfig.address', + 1, + base_tags, + hostname, + resource_metric_suffix, + additional_tags=nic_tags, + ) + + def submit_ip_stack_property_metrics( + self, + ip_stacks, # type: List[VmomiObject] + base_tags, # type: List[str] + hostname, # type: str + resource_metric_suffix, # type: str + ): + # type: (...) -> None + for ip_stack in ip_stacks: + ip_tags = {} + if ip_stack.dnsConfig is not None: + host_name = ip_stack.dnsConfig.hostName + domain_name = ip_stack.dnsConfig.domainName + ip_tags.update({'route_hostname': host_name, 'route_domain_name': domain_name}) + ip_routes = ip_stack.ipRouteConfig.ipRoute + for ip_route in ip_routes: + prefix_length = ip_route.prefixLength + gateway_address = ip_route.gateway.ipAddress + network = ip_route.network + # network + device = ip_route.gateway.device + route_tags = { + 'device': device, + 'network_dest_ip': network, + 'prefix_length': prefix_length, + 'gateway_address': gateway_address, + } + ip_tags.update(route_tags) + + self.submit_property_metric( + 'guest.ipStack.ipRoute', + 1, + base_tags, + hostname, + resource_metric_suffix, + additional_tags=ip_tags, + ) + + def submit_basic_property_metrics( + self, + all_properties, # type: Dict[str, Any] + base_tags, # type: List[str] + hostname, # type: str + resource_metric_suffix, # type: str + ): + # type: (...) -> None + for property_name in VM_SIMPLE_PROPERTIES: + property_val = all_properties.get(property_name, None) + + self.submit_property_metric( + property_name, + property_val, + base_tags, + hostname, + resource_metric_suffix, + ) + + def submit_property_metrics( + self, + resource_type, # type: Type[vim.ManagedEntity] + mor_props, # type: Dict[str, Any] + resource_tags, # type: List[str] + ): + # type: (...) -> None + if resource_type == vim.VirtualMachine: + resource_metric_suffix = MOR_TYPE_AS_STRING[resource_type] + mor_name = to_string(mor_props.get("name", "unknown")) + all_properties = mor_props.get('properties', None) + if not all_properties: + self.log.warning("Could not retrieve properties for VM %s", mor_name) + return + + hostname = mor_props.get('hostname', 'unknown') + base_tags = self._config.base_tags + resource_tags + nics = all_properties.get('guest.net', []) + self.submit_nic_property_metrics(nics, base_tags, hostname, resource_metric_suffix) + + ip_stacks = all_properties.get('guest.ipStack', []) + self.submit_ip_stack_property_metrics(ip_stacks, base_tags, hostname, resource_metric_suffix) + + disks = all_properties.get('guest.disk', []) + self.submit_disk_property_metrics(disks, base_tags, hostname, resource_metric_suffix) + + self.submit_basic_property_metrics(all_properties, base_tags, hostname, resource_metric_suffix) + def check(self, _): # type: (Any) -> None self._hostname = datadog_agent.get_hostname() @@ -697,10 +937,22 @@ def check(self, _): if self.infrastructure_cache.is_expired(): with self.infrastructure_cache.update(): self.refresh_infrastructure_cache() + # Submit host tags as soon as we have fresh data self.submit_external_host_tags() - # Submit the number of VMs that are monitored + # Submit property metrics after the cache is refreshed + if self._config.collect_property_metrics: + for resource_type in self._config.collected_resource_types: + for mor in self.infrastructure_cache.get_mors(resource_type): + mor_props = self.infrastructure_cache.get_mor_props(mor) + # Explicitly do not attach any host to those metrics. + resource_tags = mor_props.get('tags', []) + self.submit_property_metrics(resource_type, mor_props, resource_tags) + # delete property data from the cache since it won't be used until next cache refresh + self.infrastructure_cache.clear_properties() + + # Submit the number of resources that are monitored for resource_type in self._config.collected_resource_types: for mor in self.infrastructure_cache.get_mors(resource_type): mor_props = self.infrastructure_cache.get_mor_props(mor) diff --git a/vsphere/metadata.csv b/vsphere/metadata.csv index 4961fbb0598e1..bfbd29f35fb8c 100644 --- a/vsphere/metadata.csv +++ b/vsphere/metadata.csv @@ -269,7 +269,26 @@ vsphere.virtualDisk.writeIOSize.latest,gauge,,,,"Average write request size in b vsphere.virtualDisk.writeLatencyUS.latest,gauge,,microsecond,,"Write latency in microseconds",0,vsphere,virtualdisk wr lat us, vsphere.virtualDisk.writeLoadMetric.latest,gauge,,,,Storage DRS virtual disk metric for the write workload model,0,vsphere,virtualdisk writeLoadMetric latest, vsphere.virtualDisk.writeOIO.latest,gauge,,request,,Average number of outstanding write requests to the virtual disk,0,vsphere,virtualdisk writeOIO latest, -vsphere.vm.count,gauge,,,,Timeserie with value 1 for each VM. Make 'sum by {X}' queries to count all the VMs with the tag X.,0,vsphere,vm count, +vsphere.vm.count,gauge,,,,Timeseries with value 1 for each VM. Make 'sum by {X}' queries to count all the VMs with the tag X.,0,vsphere,vm count, +vsphere.vm.guest.disk.capacity,gauge,300,byte,,"Total capacity of the disk, in bytes. Tagged by `disk_path`.",0,vsphere,vm disk capacity, +vsphere.vm.guest.disk.freeSpace,gauge,300,byte,,"Free space on the disk, in bytes. Tagged by `disk_path`.",0,vsphere,vm free space, +vsphere.vm.guest.ipStack.ipRoute,count,300,,,"Count of IP Routes in the IP Route table on this guest OS",0,vsphere,vm ip route, +vsphere.vm.guest.net.ipConfig.address,count,300,,,"Number of staticly assigned IP addresses to be configured on a given interface on this guest OS. Tagged by `nic_mac_address`, `device_id`, `is_connected`, and `nic_ip_address`.",0,vsphere,vm ip address, +vsphere.vm.guest.net,count,300,,,"Number of network adapters on this guest OS. Tagged by `nic_mac_address`, `device_id`, and `is_connected`.",0,vsphere,vm net, +vsphere.vm.guest.toolsVersion,count,300,,,"Information about the current version of VMware Tools, tagged by `toolsversion`.",0,vsphere,vm tools version, +vsphere.vm.guest.toolsRunningStatus,count,300,,,"Information about the current status of VMware Tools, tagged by `toolsstatus`.",0,vsphere,vm tools status, +vsphere.vm.guest.toolsVersionStatus2,count,300,,,"Information about the current version status of VMware Tools, tagged by `toolsversionstatus2`.",0,vsphere,vm tools version status, +vsphere.vm.config.hardware.numCoresPerSocket,gauge,300,,,Number of cores used to distribute virtual CPUs among sockets in this virtual machine. If the value is unset it implies to numCoresPerSocket = 1.",0,vsphere,vm num cores, +vsphere.vm.summary.config.memorySizeMB,gauge,300,megabyte,,"Memory size of the virtual machine, in MB.",0,vsphere,vm memory size, +vsphere.vm.summary.config.numCpu,gauge,300,,,"Number of processors present in this virtual machine.",0,vsphere,vm num cpu, +vsphere.vm.summary.config.numEthernetCards,gauge,300,,,Number of virtual network adapters.,0,vsphere,vm ethernet cards, +vsphere.vm.summary.config.numVirtualDisks,gauge,300,,,Number of virtual disks attached to the virtual machine.,0,vsphere,vm virtual disks, +vsphere.vm.config.memoryAllocation.overheadLimit,gauge,300,megabyte,,"The maximum allowed overhead memory. For a powered on virtual machine, the overhead memory reservation cannot be larger than its overheadLimit. This property is only applicable to powered on virtual machines and is not persisted across reboots. This property is not applicable for resource pools. If set to -1, then there is no limit on reservation",0,vsphere,vm virtual disks, +vsphere.vm.config.memoryAllocation.limit,gauge,300,megabyte,,"The utilization of a virtual machine/resource pool will not exceed this limit, even if there are available resources. This is typically used to ensure a consistent performance of virtual machines / resource pools independent of available resources. If set to -1, then there is no fixed limit on resource usage",0,vsphere,vm mem limit, +vsphere.vm.config.cpuAllocation.overheadLimit,gauge,300,megahertz,,"The maximum allowed overhead cpu. For a powered on virtual machine, the overhead cpu reservation cannot be larger than its overheadLimit. This property is only applicable to powered on virtual machines and is not persisted across reboots. This property is not applicable for resource pools. If set to -1, then there is no limit on reservation",0,vsphere,vm cpu overhead, +vsphere.vm.config.cpuAllocation.limit,gauge,300,megahertz,,"The utilization of a virtual machine/resource pool will not exceed this limit, even if there are available resources. This is typically used to ensure a consistent performance of virtual machines / resource pools independent of available resources. If set to -1, then there is no fixed limit on resource usage",0,vsphere,vm cpu limit, +vsphere.vm.guest.guestFullName,count,300,,,"Guest operating system full name, if known, tagged by guestFullName",0,vsphere,vm guest name, +vsphere.vm.summary.quickStats.uptimeSeconds,gauge,300,second,,The system uptime of the VM in seconds.,0,vsphere,vm uptime, vsphere.host.count,gauge,,,,Timeserie with value 1 for each ESXi Host. Make 'sum by {X}' queries to count all the Hosts with the tag X.,0,vsphere,host count, vsphere.datastore.count,gauge,,,,Timeserie with value 1 for each Datastore. Make 'sum by {X}' queries to count all the Datastores with the tag X.,0,vsphere,datastore count, vsphere.datacenter.count,gauge,,,,Timeserie with value 1 for each Datacenter. Make 'sum by {X}' queries to count all the Datacenters with the tag X.,0,vsphere,datacenter count, diff --git a/vsphere/tests/common.py b/vsphere/tests/common.py index a3da0b175235c..8456fadf8a888 100644 --- a/vsphere/tests/common.py +++ b/vsphere/tests/common.py @@ -4,6 +4,7 @@ import os import re +import mock from pyVmomi import vim, vmodl from six.moves.urllib.parse import urlparse @@ -387,6 +388,361 @@ def build_rest_api_client(config, logger): ) +# VM 1 disk +disk = vim.vm.GuestInfo.DiskInfo() +disk.diskPath = '\\' +disk.capacity = 2064642048 +disk.freeSpace = 1270075392 +disk.filesystemType = 'ext4' +DISKS = vim.ArrayOfAnyType() +DISKS.append(disk) + +# VM 1 net +ip_address = vim.net.IpConfigInfo.IpAddress() +ip_address.ipAddress = 'fe70::150:46ff:fe47:6311' +ip_config = vim.net.IpConfigInfo() +ip_config.ipAddress = vim.ArrayOfAnyType() +ip_config.ipAddress.append(ip_address) +net = vim.vm.GuestInfo.NicInfo() +net.macAddress = '00:61:58:72:53:13' +net.connected = True +net.ipConfig = ip_config +NETS = vim.ArrayOfAnyType() +NETS.append(net) + +# VM 1 ip stack +dns_config = vim.net.DnsConfigInfo() +dns_config.hostName = 'test-hostname' +dns_config.domainName = 'example.com' +gateway = vim.net.IpRouteConfigInfo.Gateway() +gateway.device = '0' +gateway.ipAddress = None +ip_route = vim.net.IpRouteConfigInfo.IpRoute() +ip_route.prefixLength = 64 +ip_route.network = 'fe83::' +ip_route.gateway = gateway +ip_route_config = vim.net.IpRouteConfigInfo() +ip_route_config.ipRoute = vim.ArrayOfAnyType() +ip_route_config.ipRoute.append(ip_route) +ip_stack = vim.vm.GuestInfo.StackInfo() +ip_stack.dnsConfig = dns_config +ip_stack.ipRouteConfig = ip_route_config +IP_STACKS = vim.ArrayOfAnyType() +IP_STACKS.append(ip_stack) + +# VM 3 disk +DISKS_3 = vim.ArrayOfAnyType() + +# VM 3 net +ip_address3 = vim.net.IpConfigInfo.IpAddress() +ip_address3.ipAddress = 'fe70::150:46ff:fe47:6311' +ip_address4 = vim.net.IpConfigInfo.IpAddress() +ip_address4.ipAddress = 'fe80::170:46ff:fe27:6311' +ip_config3 = vim.net.IpConfigInfo() +ip_config3.ipAddress = vim.ArrayOfAnyType() +ip_config3.ipAddress.append(ip_address3) +ip_config3.ipAddress.append(ip_address4) +net3 = vim.vm.GuestInfo.NicInfo() +net3.macAddress = None +net3.deviceConfigId = 43 +net3.ipConfig = ip_config3 +NETS_3 = vim.ArrayOfAnyType() +NETS_3.append(net3) + +# VM 3 ip stack +gateway3 = vim.net.IpRouteConfigInfo.Gateway() +gateway3.device = '0' +gateway3.ipAddress = '0.0.0.0' +ip_route3 = vim.net.IpRouteConfigInfo.IpRoute() +ip_route3.prefixLength = 32 +ip_route3.network = 'fe83::' +ip_route3.gateway = gateway3 +ip_route_config3 = vim.net.IpRouteConfigInfo() +ip_route_config3.ipRoute = vim.ArrayOfAnyType() +ip_route_config3.ipRoute.append(ip_route3) +ip_stack3 = vim.vm.GuestInfo.StackInfo() +ip_stack3.dnsConfig = None +ip_stack3.ipRouteConfig = ip_route_config3 +IP_STACKS_3 = vim.ArrayOfAnyType() +IP_STACKS_3.append(ip_stack3) + +VM_QUERY_PERF = mock.MagicMock( + return_value=[ + vim.PerformanceManager.EntityMetric( + entity=vim.VirtualMachine(moId="vm1"), + value=[ + vim.PerformanceManager.IntSeries( + value=[47, 52], + id=vim.PerformanceManager.MetricId(counterId=103), + ) + ], + ), + vim.PerformanceManager.EntityMetric( + entity=vim.VirtualMachine(moId="vm2"), + value=[ + vim.PerformanceManager.IntSeries( + value=[30, 11], + id=vim.PerformanceManager.MetricId(counterId=103), + ) + ], + ), + vim.PerformanceManager.EntityMetric( + entity=vim.VirtualMachine(moId="vm3"), + value=[ + vim.PerformanceManager.IntSeries( + value=[30, 11], + id=vim.PerformanceManager.MetricId(counterId=103), + ) + ], + ), + ] +) + +VM_PROPERTIES_EX = mock.MagicMock( + return_value=vim.PropertyCollector.RetrieveResult( + objects=[ + vim.ObjectContent( + obj=vim.VirtualMachine(moId="vm1"), + propSet=[ + vmodl.DynamicProperty( + name='name', + val='vm1', + ), + vmodl.DynamicProperty( + name='runtime.powerState', + val=vim.VirtualMachinePowerState.poweredOn, + ), + vmodl.DynamicProperty( + name='summary.config.numCpu', + val=2, + ), + vmodl.DynamicProperty( + name='summary.config.memorySizeMB', + val=2048, + ), + vmodl.DynamicProperty( + name='summary.config.numVirtualDisks', + val=1, + ), + vmodl.DynamicProperty( + name='summary.config.numEthernetCards', + val=1, + ), + vmodl.DynamicProperty( + name='summary.quickStats.uptimeSeconds', + val=12184573, + ), + vmodl.DynamicProperty( + name='guest.guestFullName', + val=None, + ), + vmodl.DynamicProperty( + name='guest.disk', + val=DISKS, + ), + vmodl.DynamicProperty( + name='guest.net', + val=NETS, + ), + vmodl.DynamicProperty( + name='guest.ipStack', + val=IP_STACKS, + ), + vmodl.DynamicProperty( + name='guest.toolsVersion', + val='11296', + ), + vmodl.DynamicProperty( + name='config.hardware.numCoresPerSocket', + val='2', + ), + vmodl.DynamicProperty( + name='config.cpuAllocation.limit', + val='-1', + ), + vmodl.DynamicProperty( + name='config.cpuAllocation.overheadLimit', + val=None, + ), + vmodl.DynamicProperty( + name='config.memoryAllocation.limit', + val='-1', + ), + vmodl.DynamicProperty( + name='config.memoryAllocation.overheadLimit', + val=None, + ), + vmodl.DynamicProperty( + name='parent', + val=vim.Folder(moId="root"), + ), + ], + ), + vim.ObjectContent( + obj=vim.VirtualMachine(moId="vm3"), + propSet=[ + vmodl.DynamicProperty( + name='name', + val='vm3', + ), + vmodl.DynamicProperty( + name='runtime.powerState', + val=vim.VirtualMachinePowerState.poweredOn, + ), + vmodl.DynamicProperty( + name='summary.config.numCpu', + val=1, + ), + vmodl.DynamicProperty( + name='summary.config.memorySizeMB', + val=None, + ), + vmodl.DynamicProperty( + name='summary.config.numVirtualDisks', + val=3, + ), + vmodl.DynamicProperty( + name='summary.config.numEthernetCards', + val=3, + ), + vmodl.DynamicProperty( + name='summary.quickStats.uptimeSeconds', + val=1218453, + ), + vmodl.DynamicProperty( + name='guest.guestFullName', + val='Debian GNU/Linux 12 (32-bit)', + ), + vmodl.DynamicProperty(name='guest.disk', val=DISKS_3), + vmodl.DynamicProperty( + name='guest.net', + val=NETS_3, + ), + vmodl.DynamicProperty( + name='guest.ipStack', + val=IP_STACKS_3, + ), + vmodl.DynamicProperty( + name='guest.toolsRunningStatus', + val='guestToolsRunning', + ), + vmodl.DynamicProperty( + name='guest.toolsVersionStatus2', + val='guestToolsSupportedOld', + ), + vmodl.DynamicProperty( + name='guest.toolsVersion', + val='11296', + ), + vmodl.DynamicProperty( + name='config.hardware.numCoresPerSocket', + val='2', + ), + vmodl.DynamicProperty( + name='config.cpuAllocation.limit', + val='10', + ), + vmodl.DynamicProperty( + name='config.cpuAllocation.overheadLimit', + val='24', + ), + vmodl.DynamicProperty( + name='config.memoryAllocation.limit', + val='-1', + ), + vmodl.DynamicProperty( + name='config.memoryAllocation.overheadLimit', + val='59', + ), + vmodl.DynamicProperty( + name='parent', + val=vim.Folder(moId="root"), + ), + ], + ), + vim.ObjectContent( + obj=vim.VirtualMachine(moId="vm2"), + propSet=[ + vmodl.DynamicProperty( + name='name', + val='vm2', + ), + vmodl.DynamicProperty( + name='runtime.powerState', + val=vim.VirtualMachinePowerState.poweredOff, + ), + vmodl.DynamicProperty( + name='summary.config.numCpu', + val=2, + ), + vmodl.DynamicProperty( + name='summary.config.memorySizeMB', + val=2048, + ), + vmodl.DynamicProperty( + name='summary.config.numVirtualDisks', + val=1, + ), + vmodl.DynamicProperty( + name='summary.config.numEthernetCards', + val=1, + ), + vmodl.DynamicProperty( + name='summary.quickStats.uptimeSeconds', + val=12184573, + ), + vmodl.DynamicProperty( + name='guest.guestFullName', + val='Debian GNU/Linux 12 (32-bit)', + ), + vmodl.DynamicProperty(name='guest.disk', val=DISKS), + vmodl.DynamicProperty( + name='guest.net', + val=NETS, + ), + vmodl.DynamicProperty( + name='guest.ipStack', + val=IP_STACKS, + ), + vmodl.DynamicProperty( + name='guest.toolsRunningStatus', + val='guestToolsRunning', + ), + vmodl.DynamicProperty( + name='guest.toolsVersion', + val='11296', + ), + vmodl.DynamicProperty( + name='config.hardware.numCoresPerSocket', + val='2', + ), + vmodl.DynamicProperty( + name='config.cpuAllocation.limit', + val='-1', + ), + vmodl.DynamicProperty( + name='config.cpuAllocation.overheadLimit', + val=None, + ), + vmodl.DynamicProperty( + name='config.memoryAllocation.limit', + val='-1', + ), + vmodl.DynamicProperty( + name='config.memoryAllocation.overheadLimit', + val=None, + ), + vmodl.DynamicProperty( + name='parent', + val=vim.Folder(moId="root"), + ), + ], + ), + ], + ) +) + + class MockHttpV6: def __init__(self): self.exceptions = {} diff --git a/vsphere/tests/conftest.py b/vsphere/tests/conftest.py index e2a9fb04c2c91..fe61ca22865f1 100644 --- a/vsphere/tests/conftest.py +++ b/vsphere/tests/conftest.py @@ -23,6 +23,8 @@ PERF_METRIC_ID, PROPERTIES_EX, REALTIME_INSTANCE, + VM_PROPERTIES_EX, + VM_QUERY_PERF, VSPHERE_VERSION, MockHttpV6, MockHttpV7, @@ -150,6 +152,16 @@ def properties_ex(): return PROPERTIES_EX +@pytest.fixture +def vm_properties_ex(): + return VM_PROPERTIES_EX + + +@pytest.fixture +def vm_query_perf(): + return VM_QUERY_PERF + + @pytest.fixture def retrieve_properties_ex(properties_ex): def RetrievePropertiesEx(spec_set, options): diff --git a/vsphere/tests/test_check.py b/vsphere/tests/test_check.py index 5f3c970f72e59..4791f65e1c362 100644 --- a/vsphere/tests/test_check.py +++ b/vsphere/tests/test_check.py @@ -700,6 +700,7 @@ def test_no_infra_cache_no_perf_values(aggregator, realtime_instance, dd_run_che with mock.patch('pyVim.connect.SmartConnect') as mock_connect, mock.patch( 'pyVmomi.vmodl.query.PropertyCollector' ) as mock_property_collector: + event = vim.event.VmReconfiguredEvent() event.userName = "datadog" event.createdTime = get_current_datetime() diff --git a/vsphere/tests/test_unit.py b/vsphere/tests/test_unit.py index a600e1fc3e715..ea123fe9b893b 100644 --- a/vsphere/tests/test_unit.py +++ b/vsphere/tests/test_unit.py @@ -2308,3 +2308,441 @@ def test_two_checks(aggregator, dd_run_check, realtime_instance, get_timestamp): dd_run_check(check) dd_run_check(check) get_timestamp.call_count == 3 + + +def test_vm_property_metrics( + aggregator, realtime_instance, dd_run_check, caplog, service_instance, vm_properties_ex, vm_query_perf +): + realtime_instance['collect_property_metrics'] = True + + service_instance.content.rootFolder = mock.MagicMock(return_value=vim.Folder(moId="root")) + + service_instance.content.propertyCollector.RetrievePropertiesEx = vm_properties_ex + + service_instance.content.perfManager.QueryPerf = vm_query_perf + + base_tags = ['vcenter_server:FAKE', 'vsphere_folder:unknown', 'vsphere_host:unknown', 'vsphere_type:vm'] + check = VSphereCheck('vsphere', {}, [realtime_instance]) + caplog.set_level(logging.DEBUG) + dd_run_check(check) + aggregator.assert_metric('vsphere.vm.count', value=2, count=2, tags=base_tags) + + # VM 1 + aggregator.assert_metric( + 'vsphere.vm.guest.guestFullName', + count=0, + hostname='vm1', + ) + assert "Could not sumbit property metric- no metric data: name=`vm.guest.guestFullName`, " + "value=`None`, hostname=`vm1`, base tags=`['vcenter_server:FAKE', 'vsphere_host:unknown', " + "'vsphere_folder:unknown', 'vsphere_type:vm']` additional tags=`{}`" in caplog.text + + aggregator.assert_metric( + 'vsphere.vm.summary.quickStats.uptimeSeconds', + count=1, + value=12184573.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.numCpu', + count=1, + value=2.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.numEthernetCards', + count=1, + value=1.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.numVirtualDisks', + count=1, + value=1.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.memorySizeMB', + count=1, + value=2048, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.config.hardware.numCoresPerSocket', + count=1, + value=2.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.toolsVersion', + count=1, + value=1, + tags=base_tags + ['toolsVersion:11296'], + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.toolsRunningStatus', + count=0, + hostname='vm1', + ) + assert "Could not sumbit property metric- no metric data: name=`vm.guest.toolsRunningStatus`, " + "value=`None`, hostname=`vm1`, base tags=`['vcenter_server:FAKE', 'vsphere_host:unknown', " + "'vsphere_folder:unknown', 'vsphere_type:vm']` additional tags=`{}`" in caplog.text + + aggregator.assert_metric( + 'vsphere.vm.guest.net', + count=1, + value=1, + tags=base_tags + ['device_id:0', 'is_connected:True', 'nic_mac_address:00:61:58:72:53:13'], + hostname='vm1', + ) + + aggregator.assert_metric( + 'vsphere.vm.guest.net', + count=1, + value=1, + tags=base_tags + ['device_id:0', 'is_connected:True', 'nic_mac_address:00:61:58:72:53:13'], + hostname='vm1', + ) + + aggregator.assert_metric( + 'vsphere.vm.guest.net.ipConfig.address', + count=1, + value=1, + tags=base_tags + + [ + 'device_id:0', + 'is_connected:True', + 'nic_ip_address:fe70::150:46ff:fe47:6311', + 'nic_mac_address:00:61:58:72:53:13', + ], + hostname='vm1', + ) + + aggregator.assert_metric( + 'vsphere.vm.guest.ipStack.ipRoute', + count=1, + value=1, + tags=base_tags + + [ + 'device:0', + 'network_dest_ip:fe83::', + 'route_domain_name:example.com', + 'route_hostname:test-hostname', + 'prefix_length:64', + ], + hostname='vm1', + ) + + aggregator.assert_metric( + 'vsphere.vm.guest.disk.freeSpace', + count=1, + value=1270075392, + tags=base_tags + ['disk_path:\\', 'file_system_type:ext4'], + hostname='vm1', + ) + + aggregator.assert_metric( + 'vsphere.vm.guest.disk.capacity', + count=1, + value=2064642048, + tags=base_tags + ['disk_path:\\', 'file_system_type:ext4'], + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.limit', + count=1, + value=-1, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.config.memoryAllocation.limit', + count=1, + value=-1, + tags=base_tags, + hostname='vm1', + ) + + assert "Submit property metric: name=`vm.config.memoryAllocation.limit`, value=`-1.0`, " + "hostname=`vm1`, tags=`['vcenter_server:FAKE', 'vsphere_host:unknown', " + "'vsphere_folder:unknown', 'vsphere_type:vm']`, count=`False`" in caplog.text + + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.overheadLimit', + count=0, + hostname='vm1', + ) + assert "Could not sumbit property metric- unexpected metric value: " + "name=`vm.config.cpuAllocation.overheadLimit`, value=`None`, hostname=`vm1`, " + "base tags=`['vcenter_server:FAKE', 'vsphere_host:unknown', 'vsphere_folder:unknown', " + "'vsphere_type:vm']` additional tags=`{}`" in caplog.text + + aggregator.assert_metric( + 'vsphere.vm.config.memoryAllocation.overheadLimit', + count=0, + hostname='vm1', + ) + assert "Could not sumbit property metric- unexpected metric value: " + "name=`vm.config.memoryAllocation.overheadLimit`, value=`None`, hostname=`vm1`, " + "base tags=`['vcenter_server:FAKE', 'vsphere_host:unknown', 'vsphere_folder:unknown', " + "'vsphere_type:vm']` additional tags=`{}`" in caplog.text + + # VM 3 + aggregator.assert_metric( + 'vsphere.vm.guest.guestFullName', + count=1, + value=1, + tags=base_tags + ['guestFullName:Debian GNU/Linux 12 (32-bit)'], + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.numCpu', + value=1, + count=1, + hostname='vm3', + tags=base_tags, + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.numEthernetCards', + count=1, + value=3.0, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.numVirtualDisks', + count=1, + value=3.0, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.config.memorySizeMB', + count=0, + hostname='vm3', + ) + assert "Could not sumbit property metric- unexpected metric value: name=`vm.summary.config.memorySizeMB`, " + "value=`None`, hostname=`vm3`, base tags=`['vcenter_server:FAKE', 'vsphere_host:unknown', " + "'vsphere_folder:unknown', 'vsphere_type:vm']` additional tags=`{}`" in caplog.text + + aggregator.assert_metric( + 'vsphere.vm.config.hardware.numCoresPerSocket', + count=1, + value=2.0, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.toolsRunningStatus', + count=1, + value=1, + tags=base_tags + ['toolsRunningStatus:guestToolsRunning'], + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.toolsVersionStatus2', + count=1, + value=1, + tags=base_tags + ['toolsVersionStatus2:guestToolsSupportedOld'], + hostname='vm3', + ) + + aggregator.assert_metric( + 'vsphere.vm.guest.net.ipConfig.address', + count=1, + value=1, + tags=base_tags + ['device_id:43', 'is_connected:False', 'nic_ip_address:fe70::150:46ff:fe47:6311'], + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.ipStack.ipRoute', + count=1, + value=1, + tags=base_tags + ['device:0', 'gateway_address:0.0.0.0', 'network_dest_ip:fe83::', 'prefix_length:32'], + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.overheadLimit', + count=1, + value=24, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.limit', + count=1, + value=10, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.config.memoryAllocation.overheadLimit', + count=1, + value=59, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.config.memoryAllocation.limit', + count=1, + value=-1, + tags=base_tags, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.disk.freeSpace', + count=0, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.guest.disk.capacity', + count=0, + hostname='vm3', + ) + + # assert we still get VM performance counter metrics + aggregator.assert_metric('vsphere.cpu.costop.sum', count=1, hostname='vm1') + aggregator.assert_metric('vsphere.cpu.costop.sum', count=1, hostname='vm3') + + aggregator.assert_metric('datadog.vsphere.collect_events.time') + aggregator.assert_metric('datadog.vsphere.refresh_metrics_metadata_cache.time') + aggregator.assert_metric('datadog.vsphere.refresh_infrastructure_cache.time') + aggregator.assert_metric('datadog.vsphere.query_metrics.time') + + aggregator.assert_all_metrics_covered() + + +def test_vm_property_metrics_filtered( + aggregator, realtime_instance, dd_run_check, service_instance, vm_properties_ex, vm_query_perf +): + realtime_instance['collect_property_metrics'] = True + realtime_instance['resource_filters'] = [ + { + 'type': 'whitelist', + 'resource': 'vm', + 'property': 'name', + 'patterns': [ + 'vm1.*', + ], + } + ] + + service_instance.content.rootFolder = mock.MagicMock(return_value=vim.Folder(moId="root")) + + service_instance.content.propertyCollector.RetrievePropertiesEx = vm_properties_ex + + service_instance.content.perfManager.QueryPerf = vm_query_perf + + base_tags = ['vcenter_server:FAKE', 'vsphere_folder:unknown', 'vsphere_host:unknown', 'vsphere_type:vm'] + check = VSphereCheck('vsphere', {}, [realtime_instance]) + dd_run_check(check) + aggregator.assert_metric('vsphere.vm.count', value=1, count=1, tags=base_tags) + aggregator.assert_metric( + 'vsphere.vm.summary.quickStats.uptimeSeconds', + count=1, + value=12184573.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.summary.quickStats.uptimeSeconds', + count=0, + hostname='vm3', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.limit', + count=1, + value=-1, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.limit', + count=0, + hostname='vm3', + ) + + +def test_vm_property_metrics_expired_cache( + aggregator, realtime_instance, dd_run_check, service_instance, vm_properties_ex, vm_query_perf +): + + with mock.patch('datadog_checks.vsphere.cache.time') as time: + + realtime_instance['collect_property_metrics'] = True + + service_instance.content.rootFolder = mock.MagicMock(return_value=vim.Folder(moId="root")) + + service_instance.content.propertyCollector.RetrievePropertiesEx = vm_properties_ex + + service_instance.content.perfManager.QueryPerf = vm_query_perf + + base_tags = ['vcenter_server:FAKE', 'vsphere_folder:unknown', 'vsphere_host:unknown', 'vsphere_type:vm'] + + # run check with expired cache once + base_time = 1576263848 + mocked_timestamps = [base_time + 160 * i for i in range(10)] + time.time = mock.MagicMock(side_effect=mocked_timestamps) + + check = VSphereCheck('vsphere', {}, [realtime_instance]) + check.infrastructure_cache._last_ts = base_time + dd_run_check(check) + aggregator.assert_metric('vsphere.vm.count', value=2, count=2, tags=base_tags) + + aggregator.assert_metric( + 'vsphere.vm.summary.quickStats.uptimeSeconds', + count=1, + value=12184573.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.limit', + count=1, + value=-1, + tags=base_tags, + hostname='vm1', + ) + + # run check with non-expired cache once and confirm no property metrics are collected + aggregator.reset() + mocked_timestamps = [base_time + 100 * i for i in range(10)] + time.time = mock.MagicMock(side_effect=mocked_timestamps) + check.infrastructure_cache._last_ts = base_time + + assert not check.infrastructure_cache.is_expired() + + dd_run_check(check) + aggregator.assert_metric('vsphere.vm.count') + aggregator.assert_metric('datadog.vsphere.collect_events.time') + aggregator.assert_metric('datadog.vsphere.query_metrics.time') + aggregator.assert_metric('vsphere.cpu.costop.sum') + aggregator.assert_all_metrics_covered() + + assert not check.infrastructure_cache.is_expired() + assert check.infrastructure_cache.is_expired() + + # run check with expired cache again and confirm property metrics are collected again + aggregator.reset() + dd_run_check(check) + aggregator.assert_metric( + 'vsphere.vm.summary.quickStats.uptimeSeconds', + count=1, + value=12184573.0, + tags=base_tags, + hostname='vm1', + ) + aggregator.assert_metric( + 'vsphere.vm.config.cpuAllocation.limit', + count=1, + value=-1, + tags=base_tags, + hostname='vm1', + )