Repository: ansible-collections/community.vmware

Issue:
VLAN is set to 0 when migrating a VMKernel port to a VSS port group
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
When using `vmware_migrate_vmk` to migrate a VMKernel port from a DVS to a VSS port group, the resulting port group on the VSS is set to VLAN `0`.  There is no attribute to `vmware_migrate_vmk` to set the VLAN.

I think the code where the VLAN is set to `0` is on line 133 of `vmware_migrate_vmk.py`:
https://github.com/ansible-collections/community.vmware/blob/1ee484fe3f116c305d8871f80aaf5a30ecdf0e74/plugins/modules/vmware_migrate_vmk.py#L128-L136

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
community.vmware.vmware_migrate_vmk

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
ansible 2.9.27
  config file = /home/REDACTED/.ansible.cfg
  configured module search path = ['/home/REDACTED/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.6/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.6.8 (default, Mar 25 2022, 11:15:52) [GCC 8.5.0 20210514 (Red Hat 8.5.0-10)]

```

##### COLLECTION VERSION
<!--- Paste verbatim output from "ansible-galaxy collection list <namespace>.<collection>"  between the quotes
for example: ansible-galaxy collection list community.general
-->
```paste below
$ grep version ~/.ansible/collections/ansible_collections/community/vmware/MANIFEST.json 
  "version": "2.2.0",
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below
ANSIBLE_PIPELINING(/home/REDACTED/.ansible.cfg) = True
ANSIBLE_SSH_ARGS(/home/REDACTED/.ansible.cfg) = -C -o ControlMaster=auto -o ControlPersist=60s
CACHE_PLUGIN(/home/REDACTED/.ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/home/REDACTED/.ansible.cfg) = ~/.ansible/fact-cache
CACHE_PLUGIN_TIMEOUT(/home/REDACTED/.ansible.cfg) = 3600
DEFAULT_LOG_PATH(env: ANSIBLE_LOG_PATH) = /home/REDACTED/ansible.log
GALAXY_SERVER_LIST(/home/REDACTED/.ansible.cfg) = [REDACTED_ansible_galaxy']
INVENTORY_CACHE_ENABLED(/home/REDACTED/.ansible.cfg) = True
INVENTORY_CACHE_PLUGIN(/home/REDACTED/.ansible.cfg) = jsonfile
INVENTORY_CACHE_PLUGIN_CONNECTION(/home/REDACTED/.ansible.cfg) = ~/.ansible/inventory-cache
INVENTORY_CACHE_TIMEOUT(/home/REDACTED/.ansible.cfg) = 3600
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
```
$ cat /etc/os-release 
NAME="CentOS Stream"
VERSION="8"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Stream 8"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://centos.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream"
```
```
    "about_info": {
        "about_info": {
            "api_type": "VirtualCenter",
            "api_version": "6.7.3",
            "build": "19299595",
            "instance_uuid": "REDACTED",
            "license_product_name": "VMware VirtualCenter Server",
            "license_product_version": "6.0",
            "locale_build": "000",
            "locale_version": "INTL",
            "os_type": "linux-x64",
            "product_full_name": "VMware vCenter Server 6.7.0 build-19299595",
            "product_line_id": "vpx",
            "product_name": "VMware vCenter Server",
            "vendor": "VMware, Inc.",
            "version": "6.7.0"
        },
```

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
Using a (not so) minimal playbook to migrate a _VMKernel_ port from a _DVS_ to a _VSS_, I can demonstrate the _VLAN_ is not set correctly.

<!--- Paste example playbooks or commands between quotes below -->
```yaml
---
- hosts: all
  connection: local
  become: false
  gather_facts: false
  vars:
    current_portgroup: vds1-vlan-203
    current_switch: vds1
    datacenter: ExampleDC
    device: vmk1
    migrate_portgroup: vswitch0-vlan-203
    migrate_switch: vSwitch0

  tasks:
  - name: get VMKernel interface information
    community.vmware.vmware_vmkernel_info:
      hostname: "{{ vcenter_hostname }}"
      username: "{{ vcenter_username }}"
      password: "{{ vcenter_password }}"
      esxi_hostname: "{{ esxi_hostname }}"
    register: vmkernel_info
    delegate_to: localhost
    tags: always
 
  - name: display VMkernel interface information
    debug:
      msg: "{{ vmkernel_info.host_vmk_info[inventory_hostname] | selectattr('device', 'equalto', device) | list }}"
    delegate_to: localhost
    tags: always

  - name: get portgroup information
    community.vmware.vmware_dvs_portgroup_info:
      hostname: "{{ vcenter_hostname }}"
      username: "{{ vcenter_username }}"
      password: "{{ vcenter_password }}"
      datacenter: "{{ datacenter }}"
      dvswitch: "{{ current_switch }}"
      show_mac_learning: false
      show_network_policy: false
      show_port_policy: false
      show_teaming_policy: false
      show_uplinks: false
      show_vlan_info: true
    register: dvs_portgroup_info
    delegate_to: localhost
    tags: always

  - name: display portgroup information
    debug:
      msg: "{{ dvs_portgroup_info.dvs_portgroup_info[current_switch] | selectattr('portgroup_name', 'equalto', current_portgroup) | map(attribute='vlan_info') | list }}"
    delegate_to: localhost
    tags: always

  - name: migrate VMKernel interface
    community.vmware.vmware_migrate_vmk:
      hostname: "{{ vcenter_hostname }}"
      username: "{{ vcenter_username }}"
      password: "{{ vcenter_password }}"
      esxi_hostname: "{{ esxi_hostname }}"
      current_portgroup_name: "{{ current_portgroup }}"
      current_switch_name: "{{ current_switch }}"
      device: "{{ device }}"
      migrate_portgroup_name: "{{ migrate_portgroup }}"
      migrate_switch_name: "{{ migrate_switch }}"
    delegate_to: localhost
    tags:
      - never
      - migrate

  - name: get VMKernel interface information
    community.vmware.vmware_vmkernel_info:
      hostname: "{{ vcenter_hostname }}"
      username: "{{ vcenter_username }}"
      password: "{{ vcenter_password }}"
      esxi_hostname: "{{ esxi_hostname }}"
    register: vmkernel_info
    delegate_to: localhost
    tags: always
 
  - name: display VMkernel interface information
    debug:
      msg: "{{ vmkernel_info.host_vmk_info[inventory_hostname] | selectattr('device', 'equalto', device) | list }}"
    delegate_to: localhost
    tags: always

  - name: get portgroup information
    community.vmware.vmware_portgroup_info:
      hostname: "{{ vcenter_hostname }}"
      username: "{{ vcenter_username }}"
      password: "{{ vcenter_password }}"
      esxi_hostname: "{{ esxi_hostname }}"
    register: portgroup_info
    delegate_to: localhost
    tags: always

  - name: display VMkernel interface information
    debug:
      msg: "{{ portgroup_info.hosts_portgroup_info[inventory_hostname] | selectattr('portgroup', 'equalto', migrate_portgroup) | list }}"
    delegate_to: localhost
    tags: always

```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
The VLAN_should be preserved from the old port group to the new port group.


##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
The VLAN is set to `0` on the target port  group.

<!--- Paste verbatim command output between quotes -->
```paste below
$ ansible-playbook -i inventory migrate_vmk_to_vss.yml --ask-vault-pass -l esx.example.com -t migrate
Vault password: 

PLAY [all] *********************************************************************************************************************************************************************************************************

TASK [get VMKernel interface information] **************************************************************************************************************************************************************************
ok: [esx.example.com -> localhost]

TASK [display VMkernel interface information] **********************************************************************************************************************************************************************
ok: [esx.example.com -> localhost] => {
    "msg": [
        {
            "device": "vmk1",
            "dhcp": false,
            "enable_ft": false,
            "enable_management": false,
            "enable_vmotion": true,
            "enable_vsan": false,
            "ipv4_address": "10.xxx.xxx.50",
            "ipv4_subnet_mask": "255.255.252.0",
            "key": "key-vim.host.VirtualNic-vmk1",
            "mac": "00:50:56:xx:xx:xx",
            "mtu": 1500,
            "portgroup": "",
            "stack": "defaultTcpipStack"
        }
    ]
}

TASK [get portgroup information] ***********************************************************************************************************************************************************************************
ok: [esx.example.com -> localhost]

TASK [display portgroup information] *******************************************************************************************************************************************************************************
ok: [esx.example.com -> localhost] => {
    "msg": [
        {
            "pvlan": false,
            "trunk": false,
            "vlan_id": "203"
        }
    ]
}

TASK [migrate VMKernel interface] **********************************************************************************************************************************************************************************
changed: [esx.example.com -> localhost]

TASK [get VMKernel interface information] **************************************************************************************************************************************************************************
ok: [esx.example.com -> localhost]

TASK [display VMkernel interface information] **********************************************************************************************************************************************************************
ok: [esx.example.com -> localhost] => {
    "msg": [
        {
            "device": "vmk1",
            "dhcp": false,
            "enable_ft": false,
            "enable_management": false,
            "enable_vmotion": true,
            "enable_vsan": false,
            "ipv4_address": "10.xx.xx.50",
            "ipv4_subnet_mask": "255.255.252.0",
            "key": "key-vim.host.VirtualNic-vmk1",
            "mac": "00:50:56:xx:xx:xx",
            "mtu": 1500,
            "portgroup": "vswitch0-vlan-203",
            "stack": "defaultTcpipStack"
        }
    ]
}

TASK [get portgroup information] ***********************************************************************************************************************************************************************************
ok: [esx.example.com -> localhost]

TASK [display VMkernel interface information] **********************************************************************************************************************************************************************
ok: [esx.example.com -> localhost] => {
    "msg": [
        {
            "portgroup": "vswitch0-vlan-203",
            "vlan_id": 0,
            "vswitch": "vSwitch0"
        }
    ]
}

PLAY RECAP *********************************************************************************************************************************************************************************************************
esx.example.com         : ok=9    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```



Discussion:
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
> When using `vmware_migrate_vmk` to migrate a VMKernel port from a DVS to a VSS port group, the resulting port group on the VSS is set to VLAN `0`. There is no attribute to `vmware_migrate_vmk` to set the VLAN.

Yes, I think this was overlooked simply because migrating from VSS to DVS is the more common use case.

> ##### EXPECTED RESULTS
> 
> The VLAN should be preserved from the old port group to the new port group.

Yes... and no. The correct VLAN on the destination VSS might be (unusal, but possible) different from the DVS. And, of course, the uplinks of the VSS might be native ports, that is there are no VLANs tagged at all. So I think we need to have both, a way to keep the VLAN from the source portgroup and a way to set it explicitelly.

What do you think?
Several years ago, I write a PowerCLI script to migrate a VMKernel Port from a DVS to a VSS.  I modeled the script after a code from a blog post by [William Lam](https://williamlam.com/2013/11/automate-reverse-migrating-from-vsphere.html).  If I recall correctly, on a VSS, a port group used by a VMKernel port is not the same as a virtual machine port group (vmnic port group verses VM port group), so when adding or migrating a VMKernel port to a VSS, a new port group is always created.  When creating the vmnic port group, the default VLAN is 0.  The VLAN is not configurable when connecting a VMKernel port to a DVS port group because an existing virtual machine port group is required, which already has the VLAN specified.

The PowerCLI script I wrote to migrate a VMKernel port to a VSS requires a VLAN number, and IIRC, is a requirement of the API and/or SDK.  So, I think the correct way to fix the `vmware.vmware_migrate_vmk` module is to add a VLAN attribute so it can be set manually.  The default can be `0` if not specified.

If the `vmware.vmware_migrate_vmk`  module is able to also determine the VLAN from the source DVS port group, then I would consider it a nice _bonus_ to the module functionality, but I don't think it's required based on my past experience with the equivalent PowerCLI commandlets.

Resolving patch:
diff --git a/plugins/modules/vmware_migrate_vmk.py b/plugins/modules/vmware_migrate_vmk.py
--- a/plugins/modules/vmware_migrate_vmk.py
+++ b/plugins/modules/vmware_migrate_vmk.py
@@ -53,6 +53,12 @@
             - Portgroup name to migrate VMK interface to
         required: True
         type: str
+    migrate_vlan_id:
+        version_added: '2.4.0'
+        description:
+            - VLAN to use for the VMK interface when migrating from VDS to VSS
+            - Will be ignored when migrating from VSS to VDS
+        type: int
 extends_documentation_fragment:
 - community.vmware.vmware.documentation
 
@@ -91,6 +97,7 @@ def __init__(self, module):
         self.host_system = None
         self.migrate_switch_name = self.module.params['migrate_switch_name']
         self.migrate_portgroup_name = self.module.params['migrate_portgroup_name']
+        self.migrate_vlan_id = self.module.params['migrate_vlan_id']
         self.device = self.module.params['device']
         self.esxi_hostname = self.module.params['esxi_hostname']
         self.current_portgroup_name = self.module.params['current_portgroup_name']
@@ -130,7 +137,7 @@ def create_port_group_config_vds_vss(self):
         port_group_config.spec = vim.host.PortGroup.Specification()
         port_group_config.changeOperation = "add"
         port_group_config.spec.name = self.migrate_portgroup_name
-        port_group_config.spec.vlanId = 0
+        port_group_config.spec.vlanId = self.migrate_vlan_id if self.migrate_vlan_id is not None else 0
         port_group_config.spec.vswitchName = self.migrate_switch_name
         port_group_config.spec.policy = vim.host.NetworkPolicy()
         return port_group_config
@@ -209,7 +216,8 @@ def main():
                               current_switch_name=dict(required=True, type='str'),
                               current_portgroup_name=dict(required=True, type='str'),
                               migrate_switch_name=dict(required=True, type='str'),
-                              migrate_portgroup_name=dict(required=True, type='str')))
+                              migrate_portgroup_name=dict(required=True, type='str'),
+                              migrate_vlan_id=dict(required=False, type='int')))
 
     module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False)
 


Test patch: