From 86013bbbbbf0872ba77f0596b44b89a57e5505b6 Mon Sep 17 00:00:00 2001 From: "Nikitin, Alexander" Date: Sun, 27 Aug 2023 17:11:32 +0300 Subject: [PATCH 1/5] added work through vCenter Server for #1799 --- ...igure_snmp_on_esxii_hosts_via_vcenter.yaml | 2 + ...mmunity.vmware.vmware_host_snmp_module.rst | 61 +- plugins/modules/vmware_host_snmp.py | 592 ++++++++++-------- 3 files changed, 373 insertions(+), 282 deletions(-) create mode 100644 changelogs/fragmetns/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml diff --git a/changelogs/fragmetns/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml b/changelogs/fragmetns/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml new file mode 100644 index 000000000..aa4b87141 --- /dev/null +++ b/changelogs/fragmetns/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml @@ -0,0 +1,2 @@ +minor_changes: + - community.vmware.vmware_host_snmp module now can configure SNMP agent on set of hosts (list in esxi_hostname parameter or as cluster in cluster_name parameter). The ability to configure the host directly remains diff --git a/docs/community.vmware.vmware_host_snmp_module.rst b/docs/community.vmware.vmware_host_snmp_module.rst index 86f861b04..36fc8ec2b 100644 --- a/docs/community.vmware.vmware_host_snmp_module.rst +++ b/docs/community.vmware.vmware_host_snmp_module.rst @@ -32,6 +32,24 @@ Parameters Choices/Defaults Comments + + +
+ cluster_name + +
+ string +
+
added in 3.10.0
+ + + + +
Name of cluster.
+
All host system from given cluster used to manage SNMP agent.
+
Required parameter, if esxi_hostname is not set.
+ +
@@ -49,6 +67,24 @@ Parameters
List of SNMP community strings.
+ + +
+ esxi_hostname + +
+ list + / elements=string +
+
added in 3.10.0
+ + + + +
List of ESXi hostname to manage SNMP agent.
+
Required parameter, if cluster_name is not set.
+ +
@@ -356,7 +392,7 @@ Examples .. code-block:: yaml - - name: Enable and configure SNMP community + - name: Enable and configure SNMP community on standalone ESXi host community.vmware.vmware_host_snmp: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' @@ -365,11 +401,12 @@ Examples state: enabled delegate_to: localhost - - name: Configure SNMP traps and filters + - name: Configure SNMP traps and filters on cluster community.vmware.vmware_host_snmp: - hostname: '{{ esxi_hostname }}' - username: '{{ esxi_username }}' - password: '{{ esxi_password }}' + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + cluster_name: '{{ cluster_name }}' community: [ test ] trap_targets: - hostname: 192.168.1.100 @@ -384,17 +421,18 @@ Examples state: enabled delegate_to: localhost - - name: Enable and configure SNMP system contact and location + - name: Enable and configure SNMP system contact and location on simple ESXi host in vCenter community.vmware.vmware_host_snmp: - hostname: '{{ esxi_hostname }}' - username: '{{ esxi_username }}' - password: '{{ esxi_password }}' + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + esxi_hostname: '{{ esxi_hostname }}' sys_contact: "admin@testemail.com" sys_location: "Austin, USA" state: enabled delegate_to: localhost - - name: Disable SNMP + - name: Disable SNMP on standalone ESXi host community.vmware.vmware_host_snmp: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' @@ -430,7 +468,7 @@ Common return values are documented `here metadata about host system's SNMP configuration
Sample:
-
{'esxi01': {'changed': False, 'community': ['test'], 'hw_source': 'indications', 'msg': 'SNMP already configured properly', 'port': 161, 'state': 'enabled', 'trap_targets': []}}
+
{'changed': True, 'esx01.example.local': {'changed': True, 'community': ['test'], 'community_previous': [], 'hw_source': 'indications', 'log_level': 'info', 'log_level_previous': 'warning', 'msg': 'SNMP state, community list, log level, sys contact, and sys location changed', 'port': 161, 'state': 'enabled', 'state_previous': 'disabled', 'sys_contact_previous': '', 'sys_location_previous': '', 'trap_filter': None, 'trap_targets': []}, 'failed': False}
@@ -445,3 +483,4 @@ Authors ~~~~~~~ - Christian Kotte (@ckotte) +- Alexander Nikitin (@ihumster) diff --git a/plugins/modules/vmware_host_snmp.py b/plugins/modules/vmware_host_snmp.py index e0bfc404f..d9d46366b 100644 --- a/plugins/modules/vmware_host_snmp.py +++ b/plugins/modules/vmware_host_snmp.py @@ -17,10 +17,25 @@ - This module can be used to configure the embedded SNMP agent on an ESXi host. author: - Christian Kotte (@ckotte) +- Alexander Nikitin (@ihumster) notes: - You need to reset the agent (to factory defaults) if you want to clear all community strings, trap targets, or filters - SNMP v3 configuration isn't implemented yet options: + cluster_name: + description: + - Name of cluster. + - All host system from given cluster used to manage SNMP agent. + - Required parameter, if C(esxi_hostname) is not set. + type: str + version_added: '3.10.0' + esxi_hostname: + description: + - List of ESXi hostname to manage SNMP agent. + - Required parameter, if C(cluster_name) is not set. + type: list + elements: str + version_added: '3.10.0' state: description: - Enable, disable, or reset the SNMP agent. @@ -84,7 +99,7 @@ ''' EXAMPLES = r''' -- name: Enable and configure SNMP community +- name: Enable and configure SNMP community on standalone ESXi host community.vmware.vmware_host_snmp: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' @@ -93,11 +108,12 @@ state: enabled delegate_to: localhost -- name: Configure SNMP traps and filters +- name: Configure SNMP traps and filters on cluster community.vmware.vmware_host_snmp: - hostname: '{{ esxi_hostname }}' - username: '{{ esxi_username }}' - password: '{{ esxi_password }}' + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + cluster_name: '{{ cluster_name }}' community: [ test ] trap_targets: - hostname: 192.168.1.100 @@ -112,17 +128,18 @@ state: enabled delegate_to: localhost -- name: Enable and configure SNMP system contact and location +- name: Enable and configure SNMP system contact and location on simple ESXi host in vCenter community.vmware.vmware_host_snmp: - hostname: '{{ esxi_hostname }}' - username: '{{ esxi_username }}' - password: '{{ esxi_password }}' + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + esxi_hostname: '{{ esxi_hostname }}' sys_contact: "admin@testemail.com" sys_location: "Austin, USA" state: enabled delegate_to: localhost -- name: Disable SNMP +- name: Disable SNMP on standalone ESXi host community.vmware.vmware_host_snmp: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' @@ -137,22 +154,34 @@ returned: always type: dict sample: { - "esxi01": { - "changed": false, - "community": ["test"], + "changed": true, + "esx01.example.local": { + "changed": true, + "community": [ + "test" + ], + "community_previous": [], "hw_source": "indications", - "msg": "SNMP already configured properly", + "log_level": "info", + "log_level_previous": "warning", + "msg": "SNMP state, community list, log level, sys contact, and sys location changed", "port": 161, "state": "enabled", + "state_previous": "disabled", + "sys_contact_previous": "", + "sys_location_previous": "", + "trap_filter": null, "trap_targets": [] }, + "failed": false } ''' try: from pyVmomi import vim + HAS_PYVMOMI = True except ImportError: - pass + HAS_PYVMOMI = False from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec, find_obj @@ -164,20 +193,34 @@ class VmwareHostSnmp(PyVmomi): def __init__(self, module): super(VmwareHostSnmp, self).__init__(module) + self.results = {"changed": False} + self.changed = False + self.hosts = None if self.is_vcenter(): - self.module.fail_json( - msg="You have to connect directly to the ESXi host. " - "It's not possible to configure SNMP through a vCenter connection." - ) + esxi_hostname = self.params['esxi_hostname'] + cluster_name = self.params['cluster_name'] + if all([esxi_hostname, cluster_name]): + self.module.fail_json( + msg="Only one of parameters [cluster_name, esxi_hostname] is required" + ) + else: + if cluster_name: + self.hosts = self.get_all_host_objs(cluster_name=cluster_name) + if self.hosts is None: + self.module.fail_json(msg="Failed to find host system in cluster %s." % cluster_name) + if esxi_hostname: + self.hosts = self.get_all_host_objs(esxi_host_name=esxi_hostname) + if self.hosts is None: + self.module.fail_json(msg="Failed to find host system.") else: - self.host = find_obj(self.content, [vim.HostSystem], None) - if self.host is None: - self.module.fail_json(msg="Failed to find host system.") + host = find_obj(self.content, [vim.HostSystem], None) + self.hosts = list() + self.hosts.append(host) def ensure(self): """Manage SNMP configuration for an ESXi host system""" - results = dict(changed=False, result=dict()) + # results = dict(changed=False, result=dict()) snmp_state = self.params.get('state') snmp_port = self.params.get('snmp_port') community = self.params.get('community') @@ -191,271 +234,273 @@ def ensure(self): event_filter = None if trap_filter: event_filter = ';'.join(trap_filter) - changed = False reset_hint = None - changed_list = [] - results = dict(msg='') - snmp_system = self.host.configManager.snmpSystem - if snmp_system: - if snmp_system.configuration: - snmp_config_spec = snmp_system.configuration - else: - self.module.fail_json(msg="SNMP agent configuration isn't supported on the ESXi host") - else: - self.module.fail_json(msg="SNMP system isn't available on the ESXi host") - - # Check state - results['state'] = snmp_state - if snmp_state == 'reset': - changed = True - # Get previous config - if snmp_config_spec.enabled: - results['state_previous'] = 'enabled' + for host in self.hosts: + changed = False + changed_list = [] + self.results[host.name] = dict(changed=False, msg='') + snmp_system = host.configManager.snmpSystem + if snmp_system: + if snmp_system.configuration: + snmp_config_spec = snmp_system.configuration + else: + self.module.fail_json(msg="SNMP agent configuration isn't supported on the ESXi host") else: - results['state_previous'] = 'disabled' - results['port_previous'] = snmp_config_spec.port - results['community_previous'] = snmp_config_spec.readOnlyCommunities - results['trap_targets_previous'] = self.get_previous_targets(snmp_config_spec.trapTargets) - for option in snmp_config_spec.option: - if option.key == 'EnvEventSource' and option.value != hw_source: - results['hw_source_previous'] = option.value - if option.key == 'loglevel' and option.value != hw_source: - results['log_level_previous'] = option.value - if option.key == 'EventFilter' and option.value != hw_source: - results['trap_filter_previous'] = option.value.split(';') - if option.key == 'syscontact' and option.value != hw_source: - results['syscontact_previous'] = option.value - if option.key == 'syslocation' and option.value != hw_source: - results['syslocation_previous'] = option.value - # Build factory default config - destination = vim.host.SnmpSystem.SnmpConfigSpec.Destination() - destination.hostName = "" - destination.port = 0 - destination.community = "" - options = [] - options.append(self.create_option('EnvEventSource', 'indications')) - options.append(self.create_option('EventFilter', 'reset')) - snmp_config_spec = vim.host.SnmpSystem.SnmpConfigSpec() - # Looks like this value is causing the reset - snmp_config_spec.readOnlyCommunities = [""] - snmp_config_spec.trapTargets = [destination] - snmp_config_spec.port = 161 - snmp_config_spec.enabled = False - snmp_config_spec.option = options - else: - if snmp_state == 'enabled' and not snmp_config_spec.enabled: - changed = True - changed_list.append("state") - results['state_previous'] = 'disabled' - snmp_config_spec.enabled = True - elif snmp_state == 'disabled' and snmp_config_spec.enabled: - changed = True - changed_list.append("state") - results['state_previous'] = 'enabled' - snmp_config_spec.enabled = False + self.module.fail_json(msg="SNMP system isn't available on the ESXi host") - # Check port - results['port'] = snmp_port - if snmp_config_spec.port != snmp_port: - changed = True - changed_list.append("port") - results['port_previous'] = snmp_config_spec.port - snmp_config_spec.port = snmp_port - - # Check read-only community strings - results['community'] = community - if snmp_config_spec.readOnlyCommunities != community: + # Check state + self.results[host.name]['state'] = snmp_state + if snmp_state == 'reset': changed = True - changed_list.append("community list") - results['community_previous'] = snmp_config_spec.readOnlyCommunities - if community: - snmp_config_spec.readOnlyCommunities = community + # Get previous config + if snmp_config_spec.enabled: + self.results[host.name]['state_previous'] = 'enabled' else: - # Doesn't work. Need to reset config instead - # snmp_config_spec.readOnlyCommunities = [] - reset_hint = True + self.results[host.name]['state_previous'] = 'disabled' + self.results[host.name]['port_previous'] = snmp_config_spec.port + self.results[host.name]['community_previous'] = snmp_config_spec.readOnlyCommunities + self.results[host.name]['trap_targets_previous'] = self.get_previous_targets(snmp_config_spec.trapTargets) + for option in snmp_config_spec.option: + if option.key == 'EnvEventSource' and option.value != hw_source: + self.results[host.name]['hw_source_previous'] = option.value + if option.key == 'loglevel' and option.value != hw_source: + self.results[host.name]['log_level_previous'] = option.value + if option.key == 'EventFilter' and option.value != hw_source: + self.results[host.name]['trap_filter_previous'] = option.value.split(';') + if option.key == 'syscontact' and option.value != hw_source: + self.results[host.name]['syscontact_previous'] = option.value + if option.key == 'syslocation' and option.value != hw_source: + self.results[host.name]['syslocation_previous'] = option.value + # Build factory default config + destination = vim.host.SnmpSystem.SnmpConfigSpec.Destination() + destination.hostName = "" + destination.port = 0 + destination.community = "" + options = [] + options.append(self.create_option('EnvEventSource', 'indications')) + options.append(self.create_option('EventFilter', 'reset')) + snmp_config_spec = vim.host.SnmpSystem.SnmpConfigSpec() + # Looks like this value is causing the reset + snmp_config_spec.readOnlyCommunities = [""] + snmp_config_spec.trapTargets = [destination] + snmp_config_spec.port = 161 + snmp_config_spec.enabled = False + snmp_config_spec.option = options + else: + if snmp_state == 'enabled' and not snmp_config_spec.enabled: + changed = True + changed_list.append("state") + self.results[host.name]['state_previous'] = 'disabled' + snmp_config_spec.enabled = True + elif snmp_state == 'disabled' and snmp_config_spec.enabled: + changed = True + changed_list.append("state") + self.results[host.name]['state_previous'] = 'enabled' + snmp_config_spec.enabled = False + + # Check port + self.results[host.name]['port'] = snmp_port + if snmp_config_spec.port != snmp_port: + changed = True + changed_list.append("port") + self.results[host.name]['port_previous'] = snmp_config_spec.port + snmp_config_spec.port = snmp_port - # Check trap targets - results['trap_targets'] = desired_trap_targets - if snmp_config_spec.trapTargets: - if desired_trap_targets: - temp_desired_targets = [] - # Loop through desired targets - for target in desired_trap_targets: - dest_hostname, dest_port, dest_community = self.check_if_options_are_valid(target) - trap_target_found = False + # Check read-only community strings + self.results[host.name]['community'] = community + if snmp_config_spec.readOnlyCommunities != community: + changed = True + changed_list.append("community list") + self.results[host.name]['community_previous'] = snmp_config_spec.readOnlyCommunities + if community: + snmp_config_spec.readOnlyCommunities = community + else: + # Doesn't work. Need to reset config instead + # snmp_config_spec.readOnlyCommunities = [] + reset_hint = True + + # Check trap targets + self.results[host.name]['trap_targets'] = desired_trap_targets + if snmp_config_spec.trapTargets: + if desired_trap_targets: + temp_desired_targets = [] + # Loop through desired targets + for target in desired_trap_targets: + dest_hostname, dest_port, dest_community = self.check_if_options_are_valid(target) + trap_target_found = False + for trap_target in snmp_config_spec.trapTargets: + if trap_target.hostName == dest_hostname: + if trap_target.port != dest_port or trap_target.community != dest_community: + changed = True + changed_list.append("trap target '%s'" % dest_hostname) + trap_target_found = True + break + if not trap_target_found: + changed = True + changed_list.append("trap target '%s'" % dest_hostname) + # Build destination and add to temp target list + destination = self.build_destination(dest_hostname, dest_port, dest_community) + temp_desired_targets.append(destination) + # Loop through existing targets to find targets that need to be deleted for trap_target in snmp_config_spec.trapTargets: - if trap_target.hostName == dest_hostname: - if trap_target.port != dest_port or trap_target.community != dest_community: - changed = True - changed_list.append("trap target '%s'" % dest_hostname) - trap_target_found = True - break - if not trap_target_found: - changed = True - changed_list.append("trap target '%s'" % dest_hostname) - # Build destination and add to temp target list - destination = self.build_destination(dest_hostname, dest_port, dest_community) - temp_desired_targets.append(destination) - # Loop through existing targets to find targets that need to be deleted - for trap_target in snmp_config_spec.trapTargets: - target_found = False + target_found = False + for target in desired_trap_targets: + if trap_target.hostName == target.get('hostname'): + target_found = True + break + if not target_found: + changed = True + changed_list.append("trap target '%s'" % trap_target.hostName) + # Configure trap targets if something has changed + if changed: + self.results[host.name]['trap_targets_previous'] = self.get_previous_targets(snmp_config_spec.trapTargets) + snmp_config_spec.trapTargets = temp_desired_targets + else: + changed = True + changed_list.append("trap targets") + self.results[host.name]['trap_targets_previous'] = self.get_previous_targets(snmp_config_spec.trapTargets) + # Doesn't work. Need to reset config instead + # snmp_config_spec.trapTargets = [] + reset_hint = True + else: + if desired_trap_targets: + changed = True + changed_list.append("trap targets") + self.results[host.name]['trap_targets_previous'] = None + desired_targets = [] for target in desired_trap_targets: - if trap_target.hostName == target.get('hostname'): - target_found = True - break - if not target_found: + dest_hostname, dest_port, dest_community = self.check_if_options_are_valid(target) + destination = self.build_destination(dest_hostname, dest_port, dest_community) + desired_targets.append(destination) + snmp_config_spec.trapTargets = desired_targets + + # Check options + self.results[host.name]['hw_source'] = hw_source + self.results[host.name]['log_level'] = log_level + self.results[host.name]['trap_filter'] = trap_filter + event_filter_found = False + sys_contact_found = False + sys_location_found = False + if snmp_config_spec.option: + for option in snmp_config_spec.option: + if option.key == 'EnvEventSource' and option.value != hw_source: changed = True - changed_list.append("trap target '%s'" % trap_target.hostName) - # Configure trap targets if something has changed - if changed: - results['trap_targets_previous'] = self.get_previous_targets(snmp_config_spec.trapTargets) - snmp_config_spec.trapTargets = temp_desired_targets - else: + changed_list.append("HW source") + self.results[host.name]['hw_source_previous'] = option.value + option.value = hw_source + if option.key == 'loglevel' and option.value != log_level: + changed = True + changed_list.append("log level") + self.results[host.name]['log_level_previous'] = option.value + option.value = log_level + if option.key == 'EventFilter': + event_filter_found = True + if event_filter and option.value != event_filter: + changed = True + changed_list.append("trap filter") + self.results[host.name]['trap_filter_previous'] = option.value.split(';') + option.value = event_filter + if option.key == 'syscontact': + sys_contact_found = True + if sys_contact is not None and option.value != sys_contact: + changed = True + changed_list.append("sys contact") + self.results[host.name]['sys_contact_previous'] = option.value + option.value = sys_contact + if option.key == 'syslocation': + sys_location_found = True + if sys_location is not None and option.value != sys_location: + changed = True + changed_list.append("sys location") + self.results[host.name]['sys_location_previous'] = option.value + option.value = sys_location + if trap_filter and not event_filter_found: + changed = True + changed_list.append("trap filter") + self.results[host.name]['trap_filter_previous'] = [] + snmp_config_spec.option.append(self.create_option('EventFilter', event_filter)) + elif not trap_filter and event_filter_found: changed = True - changed_list.append("trap targets") - results['trap_targets_previous'] = self.get_previous_targets(snmp_config_spec.trapTargets) + changed_list.append("trap filter") + # options = [] + for option in snmp_config_spec.option: + if option.key == 'EventFilter': + self.results[host.name]['trap_filter_previous'] = option.value.split(';') + # else: + # options.append(option) # Doesn't work. Need to reset config instead - # snmp_config_spec.trapTargets = [] + # snmp_config_spec.option = options reset_hint = True - else: - if desired_trap_targets: + if sys_contact and not sys_contact_found: changed = True - changed_list.append("trap targets") - results['trap_targets_previous'] = None - desired_targets = [] - for target in desired_trap_targets: - dest_hostname, dest_port, dest_community = self.check_if_options_are_valid(target) - destination = self.build_destination(dest_hostname, dest_port, dest_community) - desired_targets.append(destination) - snmp_config_spec.trapTargets = desired_targets - - # Check options - results['hw_source'] = hw_source - results['log_level'] = log_level - results['trap_filter'] = trap_filter - event_filter_found = False - sys_contact_found = False - sys_location_found = False - if snmp_config_spec.option: - for option in snmp_config_spec.option: - if option.key == 'EnvEventSource' and option.value != hw_source: - changed = True - changed_list.append("HW source") - results['hw_source_previous'] = option.value - option.value = hw_source - if option.key == 'loglevel' and option.value != log_level: - changed = True - changed_list.append("log level") - results['log_level_previous'] = option.value - option.value = log_level - if option.key == 'EventFilter': - event_filter_found = True - if event_filter and option.value != event_filter: - changed = True - changed_list.append("trap filter") - results['trap_filter_previous'] = option.value.split(';') - option.value = event_filter - if option.key == 'syscontact': - sys_contact_found = True - if sys_contact is not None and option.value != sys_contact: - changed = True - changed_list.append("sys contact") - results['sys_contact_previous'] = option.value - option.value = sys_contact - if option.key == 'syslocation': - sys_location_found = True - if sys_location is not None and option.value != sys_location: - changed = True - changed_list.append("sys location") - results['sys_location_previous'] = option.value - option.value = sys_location - if trap_filter and not event_filter_found: - changed = True - changed_list.append("trap filter") - results['trap_filter_previous'] = [] - snmp_config_spec.option.append(self.create_option('EventFilter', event_filter)) - elif not trap_filter and event_filter_found: - changed = True - changed_list.append("trap filter") - # options = [] - for option in snmp_config_spec.option: - if option.key == 'EventFilter': - results['trap_filter_previous'] = option.value.split(';') - # else: - # options.append(option) - # Doesn't work. Need to reset config instead - # snmp_config_spec.option = options - reset_hint = True - if sys_contact and not sys_contact_found: - changed = True - changed_list.append("sys contact") - results['sys_contact_previous'] = '' - snmp_config_spec.option.append(self.create_option('syscontact', sys_contact)) - if sys_location and not sys_location_found: - changed = True - changed_list.append("sys location") - results['sys_location_previous'] = '' - snmp_config_spec.option.append(self.create_option('syslocation', sys_location)) - if changed: - if snmp_state == 'reset': - if self.module.check_mode: - message = "SNMP agent would be reset to factory defaults" + changed_list.append("sys contact") + self.results[host.name]['sys_contact_previous'] = '' + snmp_config_spec.option.append(self.create_option('syscontact', sys_contact)) + if sys_location and not sys_location_found: + changed = True + changed_list.append("sys location") + self.results[host.name]['sys_location_previous'] = '' + snmp_config_spec.option.append(self.create_option('syslocation', sys_location)) + if changed: + if snmp_state == 'reset': + if self.module.check_mode: + message = "SNMP agent would be reset to factory defaults" + else: + message = "SNMP agent config reset to factory defaults" else: - message = "SNMP agent config reset to factory defaults" + if self.module.check_mode: + changed_suffix = ' would be changed' + else: + changed_suffix = ' changed' + if len(changed_list) > 2: + message = ', '.join(changed_list[:-1]) + ', and ' + str(changed_list[-1]) + elif len(changed_list) == 2: + message = ' and '.join(changed_list) + elif len(changed_list) == 1: + message = changed_list[0] + message = "SNMP " + message + changed_suffix + if reset_hint: + message += ". Agent reset required!" + if not self.module.check_mode: + try: + snmp_system.ReconfigureSnmpAgent(snmp_config_spec) + except vim.fault.NotFound as not_found: + self.module.fail_json( + msg="Not found : %s" % to_native(not_found) + ) + except vim.fault.InsufficientResourcesFault as insufficient_resources: + self.module.fail_json( + msg="Insufficient resources : %s" % to_native(insufficient_resources) + ) else: + message = "SNMP already configured properly" + if not snmp_state == 'reset' and send_trap and desired_trap_targets: + # Check if there was a change before + if changed: + message += " and " + else: + message += ", but " + changed = True if self.module.check_mode: - changed_suffix = ' would be changed' + message = message + "a test trap would be sent" else: - changed_suffix = ' changed' - if len(changed_list) > 2: - message = ', '.join(changed_list[:-1]) + ', and ' + str(changed_list[-1]) - elif len(changed_list) == 2: - message = ' and '.join(changed_list) - elif len(changed_list) == 1: - message = changed_list[0] - message = "SNMP " + message + changed_suffix - if reset_hint: - message += ". Agent reset required!" - if not self.module.check_mode: - try: - snmp_system.ReconfigureSnmpAgent(snmp_config_spec) - except vim.fault.NotFound as not_found: - self.module.fail_json( - msg="Not found : %s" % to_native(not_found) - ) - except vim.fault.InsufficientResourcesFault as insufficient_resources: - self.module.fail_json( - msg="Insufficient resources : %s" % to_native(insufficient_resources) - ) - else: - message = "SNMP already configured properly" - if not snmp_state == 'reset' and send_trap and desired_trap_targets: - # Check if there was a change before - if changed: - message += " and " - else: - message += ", but " - changed = True - if self.module.check_mode: - message = message + "a test trap would be sent" - else: - try: - snmp_system.SendTestNotification() - message = message + "a test trap was sent" - except vim.fault.NotFound as not_found: - self.module.fail_json( - msg="Error during trap test : Not found : %s" % to_native(not_found) - ) - except vim.fault.InsufficientResourcesFault as insufficient_resources: - self.module.fail_json( - msg="Error during trap test : Insufficient resources : %s" % to_native(insufficient_resources) - ) - results['changed'] = changed - results['msg'] = message - - self.module.exit_json(**results) + try: + snmp_system.SendTestNotification() + message = message + "a test trap was sent" + except vim.fault.NotFound as not_found: + self.module.fail_json( + msg="Error during trap test : Not found : %s" % to_native(not_found) + ) + except vim.fault.InsufficientResourcesFault as insufficient_resources: + self.module.fail_json( + msg="Error during trap test : Insufficient resources : %s" % to_native(insufficient_resources) + ) + self.changed = any([self.changed, changed]) + self.results[host.name]['changed'] = changed + self.results[host.name]['msg'] = message + self.results['changed'] = self.changed + self.module.exit_json(**self.results) @staticmethod def create_option(key, value): @@ -510,6 +555,8 @@ def main(): """Main""" argument_spec = vmware_argument_spec() argument_spec.update( + cluster_name=dict(type='str', required=False), + esxi_hostname=dict(type='list', elemets='str', required=False), state=dict(type='str', default='disabled', choices=['enabled', 'disabled', 'reset']), snmp_port=dict(type='int', default=161), community=dict(type='list', default=[], elements='str'), @@ -524,9 +571,12 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, - supports_check_mode=True + supports_check_mode=True, ) + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi required for this module') + host_snmp = VmwareHostSnmp(module) host_snmp.ensure() From ab576e8a62c67594e837d25fcb96205742692079 Mon Sep 17 00:00:00 2001 From: "Nikitin, Alexander" Date: Sun, 27 Aug 2023 17:20:48 +0300 Subject: [PATCH 2/5] rename folder fragments =( --- .../1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelogs/{fragmetns => fragments}/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml (100%) diff --git a/changelogs/fragmetns/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml b/changelogs/fragments/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml similarity index 100% rename from changelogs/fragmetns/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml rename to changelogs/fragments/1799_configure_snmp_on_esxii_hosts_via_vcenter.yaml From 8b0f7ad2f737c837e3e4824b4af80e50ce81621d Mon Sep 17 00:00:00 2001 From: "Nikitin, Alexander" Date: Sun, 27 Aug 2023 17:22:27 +0300 Subject: [PATCH 3/5] fix sanity --- plugins/modules/vmware_host_snmp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/vmware_host_snmp.py b/plugins/modules/vmware_host_snmp.py index d9d46366b..b2a412d4c 100644 --- a/plugins/modules/vmware_host_snmp.py +++ b/plugins/modules/vmware_host_snmp.py @@ -556,7 +556,7 @@ def main(): argument_spec = vmware_argument_spec() argument_spec.update( cluster_name=dict(type='str', required=False), - esxi_hostname=dict(type='list', elemets='str', required=False), + esxi_hostname=dict(type='list', elements='str', required=False), state=dict(type='str', default='disabled', choices=['enabled', 'disabled', 'reset']), snmp_port=dict(type='int', default=161), community=dict(type='list', default=[], elements='str'), From 5276f33234c4323cf0953938d9118192997cfc46 Mon Sep 17 00:00:00 2001 From: Mario Lenz Date: Thu, 12 Oct 2023 17:22:40 +0200 Subject: [PATCH 4/5] Update version_added --- plugins/modules/vmware_host_snmp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/vmware_host_snmp.py b/plugins/modules/vmware_host_snmp.py index b2a412d4c..f8713b963 100644 --- a/plugins/modules/vmware_host_snmp.py +++ b/plugins/modules/vmware_host_snmp.py @@ -28,14 +28,14 @@ - All host system from given cluster used to manage SNMP agent. - Required parameter, if C(esxi_hostname) is not set. type: str - version_added: '3.10.0' + version_added: '3.11.0' esxi_hostname: description: - List of ESXi hostname to manage SNMP agent. - Required parameter, if C(cluster_name) is not set. type: list elements: str - version_added: '3.10.0' + version_added: '3.11.0' state: description: - Enable, disable, or reset the SNMP agent. From bd0bc43cfa18099aaaec76f69d5e79016696d06f Mon Sep 17 00:00:00 2001 From: Mario Lenz Date: Thu, 12 Oct 2023 19:25:36 +0200 Subject: [PATCH 5/5] Add integration tests --- .../targets/vmware_host_snmp/aliases | 2 +- .../targets/vmware_host_snmp/tasks/main.yml | 131 +++++++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/tests/integration/targets/vmware_host_snmp/aliases b/tests/integration/targets/vmware_host_snmp/aliases index 07e8732a3..d1d14d229 100644 --- a/tests/integration/targets/vmware_host_snmp/aliases +++ b/tests/integration/targets/vmware_host_snmp/aliases @@ -1,3 +1,3 @@ cloud/vcenter needs/target/prepare_vmware_tests -zuul/vmware/vcenter_1esxi +zuul/vmware/vcenter_2esxi diff --git a/tests/integration/targets/vmware_host_snmp/tasks/main.yml b/tests/integration/targets/vmware_host_snmp/tasks/main.yml index a1fad114a..d84dae45f 100644 --- a/tests/integration/targets/vmware_host_snmp/tasks/main.yml +++ b/tests/integration/targets/vmware_host_snmp/tasks/main.yml @@ -7,7 +7,6 @@ vars: setup_attach_host: true -# SNMP works only with standalone ESXi server - name: Enable and configure SNMP community in check mode vmware_host_snmp: hostname: '{{ esxi1 }}' @@ -68,3 +67,133 @@ that: - snmp_disabled is defined - snmp_disabled.changed + +- name: Enable and configure SNMP community via vCenter in check mode + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + esxi_hostname: '{{ esxi2 }}' + community: [ test ] + state: enabled + validate_certs: false + register: snmp_enabled_check_mode + check_mode: true +- debug: var=snmp_enabled_check_mode +- assert: + that: + - snmp_enabled_check_mode is defined + - snmp_enabled_check_mode.changed + +- name: Enable and configure SNMP community via vCenter + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + esxi_hostname: '{{ esxi2 }}' + community: [ test ] + state: enabled + validate_certs: false + register: snmp_enabled +- debug: var=snmp_enabled +- assert: + that: + - snmp_enabled is defined + - snmp_enabled.changed + +- name: Enable and configure SNMP system contact and location via vCenter + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + esxi_hostname: '{{ esxi2 }}' + sys_contact: "admin@testemail.com" + sys_location: "Austin, USA" + state: enabled + validate_certs: false + register: snmp_enabled_sys_options +- debug: var=snmp_enabled_sys_options +- assert: + that: + - snmp_enabled_sys_options is defined + - snmp_enabled_sys_options.changed + +- name: Disable SNMP via vCenter + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + esxi_hostname: '{{ esxi2 }}' + state: disabled + validate_certs: false + register: snmp_disabled +- debug: var=snmp_disabled +- assert: + that: + - snmp_disabled is defined + - snmp_disabled.changed + +- name: Enable and configure SNMP community in a cluster via vCenter in check mode + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + cluster_name: "{{ ccr1 }}" + community: [ test2 ] + state: enabled + validate_certs: false + register: snmp_enabled_check_mode + check_mode: true +- debug: var=snmp_enabled_check_mode +- assert: + that: + - snmp_enabled_check_mode is defined + - snmp_enabled_check_mode.changed + +- name: Enable and configure SNMP community in a cluster via vCenter + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + cluster_name: "{{ ccr1 }}" + community: [ test2 ] + state: enabled + validate_certs: false + register: snmp_enabled +- debug: var=snmp_enabled +- assert: + that: + - snmp_enabled is defined + - snmp_enabled.changed + +- name: Enable and configure SNMP system contact and location in a cluster via vCenter + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + cluster_name: "{{ ccr1 }}" + sys_contact: "admin2@testemail.com" + sys_location: "Austin, USA" + state: enabled + validate_certs: false + register: snmp_enabled_sys_options +- debug: var=snmp_enabled_sys_options +- assert: + that: + - snmp_enabled_sys_options is defined + - snmp_enabled_sys_options.changed + +- name: Disable SNMP in a cluster via vCenter + vmware_host_snmp: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + cluster_name: "{{ ccr1 }}" + state: disabled + validate_certs: false + register: snmp_disabled +- debug: var=snmp_disabled +- assert: + that: + - snmp_disabled is defined + - snmp_disabled.changed