From 930fde5f7079601c5f174bd510ef434435343dc2 Mon Sep 17 00:00:00 2001 From: Philippe Dellaert Date: Mon, 6 Nov 2017 20:46:51 +0400 Subject: [PATCH] vmware_dvs_portgroup: Add configuration of vlan trunk, security settings and port policies and integration tests (#32298) * Add configuration of vlan trunk, security settings and port policies, and tests This commit adds the following capabilities to the vmware_dvs_portgroup module: - Support for VLAN trunk portgroup - Support for all security settings (promiscuous, forged transmits & mac address changes) - Support for all the port specific policies - port specific policies match the vCenter UI behaviour (for instance: block override is enabled by default) - Cleanup and use of proper API entities not root entities - Integration testing * Cleanup of docs and adding more examples --- .../cloud/vmware/vmware_dvs_portgroup.py | 235 +++++++++++++++-- .../targets/vmware_dvs_portgroup/aliases | 3 + .../vmware_dvs_portgroup/tasks/main.yml | 244 ++++++++++++++++++ 3 files changed, 456 insertions(+), 26 deletions(-) create mode 100644 test/integration/targets/vmware_dvs_portgroup/aliases create mode 100644 test/integration/targets/vmware_dvs_portgroup/tasks/main.yml diff --git a/lib/ansible/modules/cloud/vmware/vmware_dvs_portgroup.py b/lib/ansible/modules/cloud/vmware/vmware_dvs_portgroup.py index 54c2094ec19..b2832e700a5 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_dvs_portgroup.py +++ b/lib/ansible/modules/cloud/vmware/vmware_dvs_portgroup.py @@ -17,20 +17,23 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', DOCUMENTATION = ''' --- module: vmware_dvs_portgroup -short_description: Create or remove a Distributed vSwitch portgroup +short_description: Create or remove a Distributed vSwitch portgroup. description: - - Create or remove a Distributed vSwitch portgroup + - Create or remove a Distributed vSwitch portgroup. version_added: 2.0 -author: "Joseph Callen (@jcpowermac)" +author: + - Joseph Callen (@jcpowermac) + - Philippe Dellaert (@pdellaert) notes: - Tested on vSphere 5.5 + - Tested on vSphere 6.5 requirements: - "python >= 2.6" - PyVmomi options: portgroup_name: description: - - The name of the portgroup that is to be created or deleted + - The name of the portgroup that is to be created or deleted. required: True switch_name: description: @@ -38,36 +41,133 @@ options: required: True vlan_id: description: - - The VLAN ID that should be configured with the portgroup + - The VLAN ID that should be configured with the portgroup, use 0 for no VLAN. + - 'If C(vlan_trunk) is configured to be I(true), this can be a range, example: 1-4094.' required: True num_ports: description: - - The number of ports the portgroup should contain + - The number of ports the portgroup should contain. required: True portgroup_type: description: - - See VMware KB 1022312 regarding portgroup types + - See VMware KB 1022312 regarding portgroup types. required: True choices: - 'earlyBinding' - 'lateBinding' - 'ephemeral' + state: + description: + - Determines if the portgroup should be present or not. + required: True + choices: + - 'present' + - 'absent' + version_added: '2.5' + vlan_trunk: + description: + - Indicates whether this is a VLAN trunk or not. + required: False + default: False + version_added: '2.5' + security: + description: + - Dict which configures the different security values for portgroup. + - 'Valid attributes are:' + - '- C(promiscuous) (bool): indicates whether promiscuous mode is allowed. (default: false)' + - '- C(forged_transmits) (bool): indicates whether forged transmits are allowed. (default: false)' + - '- C(mac_changes) (bool): indicates whether mac changes are allowed. (default: false)' + required: False + version_added: '2.5' + port_policy: + description: + - Dict which configures the advanced policy settings for the portgroup. + - 'Valid attributes are:' + - '- C(block_override) (bool): indicates if the block policy can be changed per port. (default: true)' + - '- C(ipfix_override) (bool): indicates if the ipfix policy can be changed per port. (default: false)' + - '- C(live_port_move) (bool): indicates if a live port can be moved in or out of the portgroup. (default: false)' + - '- C(network_rp_override) (bool): indicates if the network resource pool can be changed per port. (default: false)' + - '- C(port_config_reset_at_disconnect) (bool): indicates if the configuration of a port is reset automatically after disconnect. (default: true)' + - '- C(security_override) (bool): indicates if the security policy can be changed per port. (default: false)' + - '- C(shaping_override) (bool): indicates if the shaping policy can be changed per port. (default: false)' + - '- C(traffic_filter_override) (bool): indicates if the traffic filter can be changed per port. (default: false)' + - '- C(uplink_teaming_override) (bool): indicates if the uplink teaming policy can be changed per port. (default: false)' + - '- C(vendor_config_override) (bool): indicates if the vendor config can be changed per port. (default: false)' + - '- C(vlan_override) (bool): indicates if the vlan can be changed per port. (default: false)' + required: False + version_added: '2.5' extends_documentation_fragment: vmware.documentation ''' EXAMPLES = ''' - - name: Create Management portgroup - local_action: - module: vmware_dvs_portgroup + - name: Create vlan portgroup + connection: local + vmware_dvs_portgroup: + hostname: vcenter_ip_or_hostname + username: vcenter_username + password: vcenter_password + portgroup_name: vlan-123-portrgoup + switch_name: dvSwitch + vlan_id: 123 + num_ports: 120 + portgroup_type: earlyBinding + state: present + + - name: Create vlan trunk portgroup + connection: local + vmware_dvs_portgroup: + hostname: vcenter_ip_or_hostname + username: vcenter_username + password: vcenter_password + portgroup_name: vlan-trunk-portrgoup + switch_name: dvSwitch + vlan_id: 1-1000 + vlan_trunk: True + num_ports: 120 + portgroup_type: earlyBinding + state: present + + - name: Create no-vlan portgroup + connection: local + vmware_dvs_portgroup: hostname: vcenter_ip_or_hostname username: vcenter_username password: vcenter_password - portgroup_name: Management + portgroup_name: no-vlan-portrgoup + switch_name: dvSwitch + vlan_id: 0 + num_ports: 120 + portgroup_type: earlyBinding + state: present + + - name: Create vlan portgroup with all security and port policies + connection: local + vmware_dvs_portgroup: + hostname: vcenter_ip_or_hostname + username: vcenter_username + password: vcenter_password + portgroup_name: vlan-123-portrgoup switch_name: dvSwitch vlan_id: 123 num_ports: 120 portgroup_type: earlyBinding state: present + security: + promiscuous: yes + forged_transmits: yes + mac_changes: yes + port_policy: + block_override: yes + ipfix_override: yes + live_port_move: yes + network_rp_override: yes + port_config_reset_at_disconnect: yes + security_override: yes + shaping_override: yes + traffic_filter_override: yes + uplink_teaming_override: yes + vendor_config_override: yes + vlan_override: yes ''' try: @@ -92,6 +192,21 @@ class VMwareDvsPortgroup(object): self.portgroup_type = self.module.params['portgroup_type'] self.dv_switch = None self.state = self.module.params['state'] + self.vlan_trunk = self.module.params['vlan_trunk'] + self.security_promiscuous = self.module.params['security']['promiscuous'] + self.security_forged_transmits = self.module.params['security']['forged_transmits'] + self.security_mac_changes = self.module.params['security']['mac_changes'] + self.policy_block_override = self.module.params['port_policy']['block_override'] + self.policy_ipfix_override = self.module.params['port_policy']['ipfix_override'] + self.policy_live_port_move = self.module.params['port_policy']['live_port_move'] + self.policy_network_rp_override = self.module.params['port_policy']['network_rp_override'] + self.policy_port_config_reset_at_disconnect = self.module.params['port_policy']['port_config_reset_at_disconnect'] + self.policy_security_override = self.module.params['port_policy']['security_override'] + self.policy_shaping_override = self.module.params['port_policy']['shaping_override'] + self.policy_traffic_filter_override = self.module.params['port_policy']['traffic_filter_override'] + self.policy_uplink_teaming_override = self.module.params['port_policy']['uplink_teaming_override'] + self.policy_vendor_config_override = self.module.params['port_policy']['vendor_config_override'] + self.policy_vlan_override = self.module.params['port_policy']['vlan_override'] self.content = connect_to_api(module) def process_state(self): @@ -118,19 +233,40 @@ class VMwareDvsPortgroup(object): def create_port_group(self): config = vim.dvs.DistributedVirtualPortgroup.ConfigSpec() + # Basic config config.name = self.portgroup_name config.numPorts = self.num_ports - # vim.VMwareDVSPortSetting() does not exist in the pyvmomi documentation - # but this is the correct managed object type. - - config.defaultPortConfig = vim.VMwareDVSPortSetting() - - # vim.VmwareDistributedVirtualSwitchVlanIdSpec() does not exist in the - # pyvmomi documentation but this is the correct managed object type - config.defaultPortConfig.vlan = vim.VmwareDistributedVirtualSwitchVlanIdSpec() + # Default port config + config.defaultPortConfig = vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy() + if self.vlan_trunk: + config.defaultPortConfig.vlan = vim.dvs.VmwareDistributedVirtualSwitch.TrunkVlanSpec() + vlan_id_start, vlan_id_end = self.vlan_id.split('-') + config.defaultPortConfig.vlan.vlanId = [vim.NumericRange(start=int(vlan_id_start.strip()), end=int(vlan_id_end.strip()))] + else: + config.defaultPortConfig.vlan = vim.dvs.VmwareDistributedVirtualSwitch.VlanIdSpec() + config.defaultPortConfig.vlan.vlanId = int(self.vlan_id) config.defaultPortConfig.vlan.inherited = False - config.defaultPortConfig.vlan.vlanId = self.vlan_id + config.defaultPortConfig.securityPolicy = vim.dvs.VmwareDistributedVirtualSwitch.SecurityPolicy() + config.defaultPortConfig.securityPolicy.allowPromiscuous = vim.BoolPolicy(value=self.security_promiscuous) + config.defaultPortConfig.securityPolicy.forgedTransmits = vim.BoolPolicy(value=self.security_forged_transmits) + config.defaultPortConfig.securityPolicy.macChanges = vim.BoolPolicy(value=self.security_mac_changes) + + # PG policy (advanced_policy) + config.policy = vim.dvs.VmwareDistributedVirtualSwitch.VMwarePortgroupPolicy() + config.policy.blockOverrideAllowed = self.policy_block_override + config.policy.ipfixOverrideAllowed = self.policy_ipfix_override + config.policy.livePortMovingAllowed = self.policy_live_port_move + config.policy.networkResourcePoolOverrideAllowed = self.policy_network_rp_override + config.policy.portConfigResetAtDisconnect = self.policy_port_config_reset_at_disconnect + config.policy.securityPolicyOverrideAllowed = self.policy_security_override + config.policy.shapingOverrideAllowed = self.policy_shaping_override + config.policy.trafficFilterOverrideAllowed = self.policy_traffic_filter_override + config.policy.uplinkTeamingOverrideAllowed = self.policy_uplink_teaming_override + config.policy.vendorConfigOverrideAllowed = self.policy_vendor_config_override + config.policy.vlanOverrideAllowed = self.policy_vlan_override + + # PG Type config.type = self.portgroup_type spec = [config] @@ -176,12 +312,59 @@ class VMwareDvsPortgroup(object): def main(): argument_spec = vmware_argument_spec() - argument_spec.update(dict(portgroup_name=dict(required=True, type='str'), - switch_name=dict(required=True, type='str'), - vlan_id=dict(required=True, type='int'), - num_ports=dict(required=True, type='int'), - portgroup_type=dict(required=True, choices=['earlyBinding', 'lateBinding', 'ephemeral'], type='str'), - state=dict(default='present', choices=['present', 'absent'], type='str'))) + argument_spec.update( + dict( + portgroup_name=dict(required=True, type='str'), + switch_name=dict(required=True, type='str'), + vlan_id=dict(required=True, type='str'), + num_ports=dict(required=True, type='int'), + portgroup_type=dict(required=True, choices=['earlyBinding', 'lateBinding', 'ephemeral'], type='str'), + state=dict(required=True, choices=['present', 'absent'], type='str'), + vlan_trunk=dict(type='bool', default=False), + security=dict( + type='dict', + options=dict( + promiscuous=dict(type='bool', default=False), + forged_transmits=dict(type='bool', default=False), + mac_changes=dict(type='bool', default=False) + ), + default=dict( + promiscuous=False, + forged_transmits=False, + mac_changes=False + ) + ), + port_policy=dict( + type='dict', + options=dict( + block_override=dict(type='bool', default=True), + ipfix_override=dict(type='bool', default=False), + live_port_move=dict(type='bool', default=False), + network_rp_override=dict(type='bool', default=False), + port_config_reset_at_disconnect=dict(type='bool', default=True), + security_override=dict(type='bool', default=False), + shaping_override=dict(type='bool', default=False), + traffic_filter_override=dict(type='bool', default=False), + uplink_teaming_override=dict(type='bool', default=False), + vendor_config_override=dict(type='bool', default=False), + vlan_override=dict(type='bool', default=False) + ), + default=dict( + block_override=True, + ipfix_override=False, + live_port_move=False, + network_rp_override=False, + port_config_reset_at_disconnect=True, + security_override=False, + shaping_override=False, + traffic_filter_override=False, + uplink_teaming_override=False, + vendor_config_override=False, + vlan_override=False + ) + ) + ) + ) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) diff --git a/test/integration/targets/vmware_dvs_portgroup/aliases b/test/integration/targets/vmware_dvs_portgroup/aliases new file mode 100644 index 00000000000..4c6228bf7a7 --- /dev/null +++ b/test/integration/targets/vmware_dvs_portgroup/aliases @@ -0,0 +1,3 @@ +posix/ci/cloud/group1/vcenter +cloud/vcenter +destructive diff --git a/test/integration/targets/vmware_dvs_portgroup/tasks/main.yml b/test/integration/targets/vmware_dvs_portgroup/tasks/main.yml new file mode 100644 index 00000000000..fc0875e9df2 --- /dev/null +++ b/test/integration/targets/vmware_dvs_portgroup/tasks/main.yml @@ -0,0 +1,244 @@ +# Test code for the vmware_dvs_portgroup module. +# (c) 2017, Philippe Dellaert + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +- name: make sure pyvmomi is installed + pip: + name: pyvmomi + state: latest + when: "{{ ansible_user_id == 'root' }}" + +- name: store the vcenter container ip + set_fact: + vcsim: "{{ lookup('env', 'vcenter_host') }}" + +- debug: var=vcsim + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 5000 + state: started + +- name: kill vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/killall' }}" + +- name: start vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/spawn?cluster=2' }}" + register: vcsim_instance + +- name: Wait for vcsim server to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- debug: var=vcsim_instance + +- name: get a list of distributed vswitch from vcsim after adding + uri: + url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=DVS' }}" + register: new_dvs_0001 + +- debug: + msg: "{{ item | basename }}" + with_items: "{{ new_dvs_0001['json'] }}" + +# Testcase 0001: Add basic portgroup +- name: create basic portgroup + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic" + vlan_id: 0 + num_ports: 32 + portgroup_type: earlyBinding + state: present + register: dvs_pg_result_0001 + +- name: ensure dvs portgroup is present + assert: + that: + - "{{ dvs_pg_result_0001.changed == true }}" + +# Testcase 0002: Add basic VLAN portgroup +- name: create basic VLAN portgroup + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic-vlan10" + vlan_id: 10 + num_ports: 32 + portgroup_type: earlyBinding + state: present + register: dvs_pg_result_0002 + +- name: ensure dvs portgroup is present + assert: + that: + - "{{ dvs_pg_result_0002.changed == true }}" + +# Testcase 0003: Add basic trunk portgroup +- name: create basic trunk portgroup + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic-trunk" + vlan_id: 1-4096 + vlan_trunk: True + num_ports: 32 + portgroup_type: earlyBinding + state: present + register: dvs_pg_result_0003 + +- name: ensure dvs portgroup is present + assert: + that: + - "{{ dvs_pg_result_0003.changed == true }}" + +# Testcase 0004: Add basic portgroup again +- name: create basic portgroup again + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic" + vlan_id: 0 + num_ports: 32 + portgroup_type: earlyBinding + state: present + register: dvs_pg_result_0004 + +- name: ensure dvs portgroup is present + assert: + that: + - "{{ dvs_pg_result_0004.changed == false }}" + +# Testcase 0005: Add basic portgroup with all security and policy settings enabled +- name: create basic portgroup with all security and policy settings enabled + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic-all-enabled" + vlan_id: 0 + num_ports: 32 + portgroup_type: earlyBinding + state: present + security: + promiscuous: yes + forged_transmits: yes + mac_changes: yes + port_policy: + block_override: yes + ipfix_override: yes + live_port_move: yes + network_rp_override: yes + port_config_reset_at_disconnect: yes + security_override: yes + shaping_override: yes + traffic_filter_override: yes + uplink_teaming_override: yes + vendor_config_override: yes + vlan_override: yes + register: dvs_pg_result_0005 + +- name: ensure dvs portgroup is present + assert: + that: + - "{{ dvs_pg_result_0005.changed == true }}" + +# Testcase 0006: Add basic portgroup with some settings enabled +- name: create basic portgroup with all security and policy settings enabled + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic-some-enabled" + vlan_id: 0 + num_ports: 32 + portgroup_type: earlyBinding + state: present + security: + promiscuous: yes + forged_transmits: yes + mac_changes: no + port_policy: + vlan_override: yes + register: dvs_pg_result_0006 + +- name: ensure dvs portgroup is present + assert: + that: + - "{{ dvs_pg_result_0006.changed == true }}" + +# Testcase 0007: Delete basic portgroup +- name: delete basic portgroup + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic" + vlan_id: 0 + num_ports: 32 + portgroup_type: earlyBinding + state: absent + register: dvs_pg_result_0007 + +- name: ensure dvs portgroup is removed + assert: + that: + - "{{ dvs_pg_result_0007.changed == true }}" + +# Testcase 0008: Delete basic portgroup again +- name: delete basic portgroup again + vmware_dvs_portgroup: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + switch_name: "{{ new_dvs_0001['json'][0] | basename }}" + portgroup_name: "basic" + vlan_id: 0 + num_ports: 32 + portgroup_type: earlyBinding + state: absent + register: dvs_pg_result_0008 + +- name: ensure dvs portgroup is removed + assert: + that: + - "{{ dvs_pg_result_0008.changed == false }}"