1#!/usr/local/bin/python3.8 2# Copyright: (c) 2020, Ansible Project 3# Copyright: (c) 2019, Diane Wang <dianew@vmware.com> 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8 9DOCUMENTATION = r''' 10--- 11module: vmware_guest_network 12short_description: Manage network adapters of specified virtual machine in given vCenter infrastructure 13description: 14 - This module is used to add, reconfigure, remove network adapter of given virtual machine. 15version_added: '1.0.0' 16requirements: 17 - "python >= 2.7" 18 - "PyVmomi" 19author: 20 - Diane Wang (@Tomorrow9) <dianew@vmware.com> 21notes: 22 - Tested on vSphere 6.0, 6.5 and 6.7 23 - For backwards compatibility network_data is returned when using the gather_network_info and networks parameters 24options: 25 name: 26 description: 27 - Name of virtual machine 28 - Required if C(uuid) or C(moid) is not supplied. 29 type: str 30 uuid: 31 description: 32 - vm uuid 33 - Required if C(name) or C(moid) is not supplied. 34 type: str 35 use_instance_uuid: 36 description: 37 - Whether to use the VMware instance UUID rather than the BIOS UUID. 38 default: False 39 type: bool 40 moid: 41 description: 42 - Managed Object ID of the instance to manage if known, this is a unique identifier only within a single vCenter instance. 43 - Required if C(uuid) or C(name) is not supplied. 44 type: str 45 folder: 46 description: 47 - Folder location of given VM, this is only required when there's multiple VM's with the same name. 48 type: str 49 datacenter: 50 default: ha-datacenter 51 description: 52 - Datacenter the VM belongs to. 53 type: str 54 cluster: 55 description: 56 - Name of cluster where VM belongs to. 57 type: str 58 esxi_hostname: 59 description: 60 - The hostname of the ESXi host where the VM belongs to. 61 type: str 62 mac_address: 63 description: 64 - MAC address of the NIC that should be altered, if a MAC address is not supplied a new nic will be created. 65 - Required when I(state=absent). 66 type: str 67 vlan_id: 68 description: 69 - VLAN id associated with the network. 70 type: int 71 network_name: 72 description: 73 - Name of network in vSphere. 74 type: str 75 device_type: 76 default: vmxnet3 77 description: 78 - Type of virtual network device. 79 - 'Valid choices are - C(e1000), C(e1000e), C(pcnet32), C(vmxnet2), C(vmxnet3) (default), C(sriov).' 80 type: str 81 label: 82 description: 83 - Alter the name of the network adapter. 84 type: str 85 switch: 86 description: 87 - Name of the (dv)switch for destination network, this is only required for dvswitches. 88 type: str 89 guest_control: 90 default: true 91 description: 92 - Enables guest control over whether the connectable device is connected. 93 type: bool 94 state: 95 default: present 96 choices: [ 'present', 'absent' ] 97 description: 98 - NIC state. 99 - When C(state=present), a nic will be added if a mac address or label does not previously exists or is unset. 100 - When C(state=absent), the I(mac_address) parameter has to be set. 101 type: str 102 start_connected: 103 default: True 104 description: 105 - If NIC should be connected to network on startup. 106 type: bool 107 wake_onlan: 108 default: False 109 description: 110 - Enable wake on LAN. 111 type: bool 112 connected: 113 default: True 114 description: 115 - If NIC should be connected to the network. 116 type: bool 117 directpath_io: 118 default: False 119 description: 120 - Enable Universal Pass-through (UPT). 121 - Only compatible with the C(vmxnet3) device type. 122 type: bool 123 force: 124 default: false 125 description: 126 - Force adapter creation even if an existing adapter is attached to the same network. 127 type: bool 128 gather_network_info: 129 aliases: 130 - gather_network_facts 131 default: False 132 description: 133 - Return information about current guest network adapters. 134 type: bool 135 networks: 136 type: list 137 elements: dict 138 description: 139 - This method will be deprecated, use loops in your playbook for multiple interfaces instead. 140 - A list of network adapters. 141 - C(mac) or C(label) or C(device_type) is required to reconfigure or remove an existing network adapter. 142 - 'If there are multiple network adapters with the same C(device_type), you should set C(label) or C(mac) to match 143 one of them, or will apply changes on all network adapters with the C(device_type) specified.' 144 - 'C(mac), C(label), C(device_type) is the order of precedence from greatest to least if all set.' 145 suboptions: 146 mac: 147 type: str 148 description: 149 - MAC address of the existing network adapter to be reconfigured or removed. 150 label: 151 type: str 152 description: 153 - Label of the existing network adapter to be reconfigured or removed, e.g., "Network adapter 1". 154 device_type: 155 type: str 156 description: 157 - 'Valid virtual network device types are C(e1000), C(e1000e), C(pcnet32), C(vmxnet2), C(vmxnet3) (default), C(sriov).' 158 - Used to add new network adapter, reconfigure or remove the existing network adapter with this type. 159 - If C(mac) and C(label) not specified or not find network adapter by C(mac) or C(label) will use this parameter. 160 name: 161 type: str 162 description: 163 - Name of the portgroup or distributed virtual portgroup for this interface. 164 - When specifying distributed virtual portgroup make sure given C(esxi_hostname) or C(cluster) is associated with it. 165 vlan: 166 type: int 167 description: 168 - VLAN number for this interface. 169 dvswitch_name: 170 type: str 171 description: 172 - Name of the distributed vSwitch. 173 - This value is required if multiple distributed portgroups exists with the same name. 174 state: 175 type: str 176 description: 177 - State of the network adapter. 178 - If set to C(present), then will do reconfiguration for the specified network adapter. 179 - If set to C(new), then will add the specified network adapter. 180 - If set to C(absent), then will remove this network adapter. 181 manual_mac: 182 type: str 183 description: 184 - Manual specified MAC address of the network adapter when creating, or reconfiguring. 185 - If not specified when creating new network adapter, mac address will be generated automatically. 186 - When reconfigure MAC address, VM should be in powered off state. 187 connected: 188 type: bool 189 description: 190 - Indicates that virtual network adapter connects to the associated virtual machine. 191 start_connected: 192 type: bool 193 description: 194 - Indicates that virtual network adapter starts with associated virtual machine powers on. 195 directpath_io: 196 type: bool 197 description: 198 - If set, Universal Pass-Through (UPT or DirectPath I/O) will be enabled on the network adapter. 199 - UPT is only compatible for Vmxnet3 adapter. 200extends_documentation_fragment: 201- community.vmware.vmware.documentation 202''' 203 204EXAMPLES = r''' 205- name: change network for 00:50:56:11:22:33 on vm01.domain.fake 206 community.vmware.vmware_guest_network: 207 hostname: "{{ vcenter_hostname }}" 208 username: "{{ vcenter_username }}" 209 password: "{{ vcenter_password }}" 210 datacenter: "{{ datacenter_name }}" 211 name: vm01.domain.fake 212 mac_address: 00:50:56:11:22:33 213 network_name: admin-network 214 state: present 215 216- name: add a nic on network with vlan id 2001 for 422d000d-2000-ffff-0000-b00000000000 217 community.vmware.vmware_guest_network: 218 hostname: "{{ vcenter_hostname }}" 219 username: "{{ vcenter_username }}" 220 password: "{{ vcenter_password }}" 221 datacenter: "{{ datacenter_name }}" 222 uuid: 422d000d-2000-ffff-0000-b00000000000 223 vlan_id: 2001 224 225- name: remove nic with mac 00:50:56:11:22:33 from vm01.domain.fake 226 community.vmware.vmware_guest_network: 227 hostname: "{{ vcenter_hostname }}" 228 username: "{{ vcenter_username }}" 229 password: "{{ vcenter_password }}" 230 datacenter: "{{ datacenter_name }}" 231 mac_address: 00:50:56:11:22:33 232 name: vm01.domain.fake 233 state: absent 234 235- name: add multiple nics to vm01.domain.fake 236 community.vmware.vmware_guest_network: 237 hostname: "{{ vcenter_hostname }}" 238 username: "{{ vcenter_username }}" 239 password: "{{ vcenter_password }}" 240 datacenter: "{{ datacenter_name }}" 241 name: vm01.domain.fake 242 state: present 243 vlan_id: "{{ item.vlan_id | default(omit) }}" 244 network_name: "{{ item.network_name | default(omit) }}" 245 connected: "{{ item.connected | default(omit) }}" 246 loop: 247 - vlan_id: 2000 248 connected: false 249 - network_name: guest-net 250 connected: true 251''' 252 253RETURN = r''' 254network_info: 255 description: metadata about the virtual machine network adapters 256 returned: always 257 type: list 258 sample: 259 "network_info": [ 260 { 261 "mac_address": "00:50:56:AA:AA:AA", 262 "allow_guest_ctl": true, 263 "connected": true, 264 "device_type": "vmxnet3", 265 "label": "Network adapter 2", 266 "network_name": "admin-net", 267 "start_connected": true, 268 "switch": "vSwitch0", 269 "unit_number": 8, 270 "vlan_id": 10, 271 "wake_onlan": false 272 }, 273 { 274 "mac_address": "00:50:56:BB:BB:BB", 275 "allow_guest_ctl": true, 276 "connected": true, 277 "device_type": "vmxnet3", 278 "label": "Network adapter 1", 279 "network_name": "guest-net", 280 "start_connected": true, 281 "switch": "vSwitch0", 282 "unit_number": 7, 283 "vlan_id": 10, 284 "wake_onlan": true 285 } 286 ] 287network_data: 288 description: For backwards compatibility, metadata about the virtual machine network adapters 289 returned: when using gather_network_info or networks parameters 290 type: dict 291 sample: 292 "network_data": { 293 '0': { 294 "mac_addr": "00:50:56:AA:AA:AA", 295 "mac_address": "00:50:56:AA:AA:AA", 296 "allow_guest_ctl": true, 297 "connected": true, 298 "device_type": "vmxnet3", 299 "label": "Network adapter 2", 300 "name": "admin-net", 301 "network_name": "admin-net", 302 "start_connected": true, 303 "switch": "vSwitch0", 304 "unit_number": 8, 305 "vlan_id": 10, 306 "wake_onlan": false 307 }, 308 '1': { 309 "mac_addr": "00:50:56:BB:BB:BB", 310 "mac_address": "00:50:56:BB:BB:BB", 311 "allow_guest_ctl": true, 312 "connected": true, 313 "device_type": "vmxnet3", 314 "label": "Network adapter 1", 315 "name": "guest-net", 316 "network_name": "guest-net", 317 "start_connected": true, 318 "switch": "vSwitch0", 319 "unit_number": 7, 320 "vlan_id": 10, 321 "wake_onlan": true 322 } 323 } 324 325''' 326 327try: 328 from pyVmomi import vim 329except ImportError: 330 pass 331 332import copy 333from ansible.module_utils.basic import AnsibleModule 334from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec, wait_for_task 335 336 337class PyVmomiHelper(PyVmomi): 338 def __init__(self, module): 339 super(PyVmomiHelper, self).__init__(module) 340 self.change_detected = False 341 self.nic_device_type = dict( 342 pcnet32=vim.vm.device.VirtualPCNet32, 343 vmxnet2=vim.vm.device.VirtualVmxnet2, 344 vmxnet3=vim.vm.device.VirtualVmxnet3, 345 e1000=vim.vm.device.VirtualE1000, 346 e1000e=vim.vm.device.VirtualE1000e, 347 sriov=vim.vm.device.VirtualSriovEthernetCard, 348 ) 349 350 def _get_network_object(self, vm_obj, network_params=None): 351 ''' 352 return network object matching given parameters 353 :param vm_obj: vm object 354 :param network_params: dict containing parameters from deprecated networks list method 355 :return: network object 356 :rtype: object 357 ''' 358 if not self.params['esxi_hostname'] or not self.params['cluster']: 359 compute_resource = vm_obj.runtime.host 360 else: 361 compute_resource = self._get_compute_resource_by_name() 362 363 pg_lookup = {} 364 if network_params: 365 vlan_id = network_params['vlan_id'] 366 network_name = network_params['network_name'] 367 switch_name = network_params['switch'] 368 else: 369 vlan_id = self.params['vlan_id'] 370 network_name = self.params['network_name'] 371 switch_name = self.params['switch'] 372 373 for pg in vm_obj.runtime.host.config.network.portgroup: 374 pg_lookup[pg.spec.name] = {'switch': pg.spec.vswitchName, 'vlan_id': pg.spec.vlanId} 375 376 if compute_resource: 377 for network in compute_resource.network: 378 if isinstance(network, vim.dvs.DistributedVirtualPortgroup): 379 dvs = network.config.distributedVirtualSwitch 380 if (switch_name and dvs.config.name == switch_name) or not switch_name: 381 if network.config.name == network_name: 382 return network 383 if hasattr(network.config.defaultPortConfig.vlan, 'vlanId') and \ 384 network.config.defaultPortConfig.vlan.vlanId == vlan_id: 385 return network 386 if hasattr(network.config.defaultPortConfig.vlan, 'pvlanId') and \ 387 network.config.defaultPortConfig.vlan.pvlanId == vlan_id: 388 return network 389 elif isinstance(network, vim.Network): 390 if network_name and network_name == network.name: 391 return network 392 if vlan_id: 393 for k in pg_lookup.keys(): 394 if vlan_id == pg_lookup[k]['vlan_id']: 395 if k == network.name: 396 return network 397 break 398 return None 399 400 def _get_vlanid_from_network(self, network): 401 ''' 402 get the vlan id from network object 403 :param network: network object to expect, either vim.Network or vim.dvs.DistributedVirtualPortgroup 404 :return: vlan id as an integer 405 :rtype: integer 406 ''' 407 vlan_id = None 408 if isinstance(network, vim.dvs.DistributedVirtualPortgroup): 409 vlan_id = network.config.defaultPortConfig.vlan.vlanId 410 411 if isinstance(network, vim.Network) and hasattr(network, 'host'): 412 for host in network.host: 413 for pg in host.config.network.portgroup: 414 if pg.spec.name == network.name: 415 vlan_id = pg.spec.vlanId 416 return vlan_id 417 418 return vlan_id 419 420 def _get_nics_from_vm(self, vm_obj): 421 ''' 422 return a list of dictionaries containing vm nic info and 423 a list of objects 424 :param vm_obj: object containing virtual machine 425 :return: list of dicts and list ith nic object(s) 426 :rtype: list, list 427 ''' 428 nic_info_lst = [] 429 nics = [nic for nic in vm_obj.config.hardware.device if isinstance(nic, vim.vm.device.VirtualEthernetCard)] 430 for nic in nics: 431 # common items of nic parameters 432 d_item = dict( 433 mac_address=nic.macAddress, 434 label=nic.deviceInfo.label, 435 unit_number=nic.unitNumber, 436 wake_onlan=nic.wakeOnLanEnabled, 437 allow_guest_ctl=nic.connectable.allowGuestControl, 438 connected=nic.connectable.connected, 439 start_connected=nic.connectable.startConnected, 440 ) 441 # If a distributed port group specified 442 if isinstance(nic.backing, vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo): 443 key = nic.backing.port.portgroupKey 444 for portgroup in vm_obj.network: 445 if hasattr(portgroup, 'key') and portgroup.key == key: 446 d_item['network_name'] = portgroup.name 447 d_item['switch'] = portgroup.config.distributedVirtualSwitch.name 448 break 449 # If an NSX-T port group specified 450 elif isinstance(nic.backing, vim.vm.device.VirtualEthernetCard.OpaqueNetworkBackingInfo): 451 d_item['network_name'] = nic.backing.opaqueNetworkId 452 d_item['switch'] = nic.backing.opaqueNetworkType 453 # If a port group specified 454 elif isinstance(nic.backing, vim.vm.device.VirtualEthernetCard.NetworkBackingInfo): 455 d_item['network_name'] = nic.backing.network.name 456 d_item['vlan_id'] = self._get_vlanid_from_network(nic.backing.network) 457 if isinstance(nic.backing.network, vim.Network): 458 for pg in vm_obj.runtime.host.config.network.portgroup: 459 if pg.spec.name == nic.backing.network.name: 460 d_item['switch'] = pg.spec.vswitchName 461 break 462 463 for k in self.nic_device_type: 464 if isinstance(nic, self.nic_device_type[k]): 465 d_item['device_type'] = k 466 break 467 468 nic_info_lst.append(d_item) 469 470 nic_info_lst = sorted(nic_info_lst, key=lambda d: d['mac_address'] if (d['mac_address'] is not None) else '00:00:00:00:00:00') 471 return nic_info_lst, nics 472 473 def _get_compute_resource_by_name(self, recurse=True): 474 ''' 475 get compute resource object with matching name of esxi_hostname or cluster 476 parameters. 477 :param recurse: recurse vmware content folder, default is True 478 :return: object matching vim.ComputeResource or None if no match 479 :rtype: object 480 ''' 481 resource_name = None 482 if self.params['esxi_hostname']: 483 resource_name = self.params['esxi_hostname'] 484 485 if self.params['cluster']: 486 resource_name = self.params['cluster'] 487 488 container = self.content.viewManager.CreateContainerView(self.content.rootFolder, [vim.ComputeResource], recurse) 489 for obj in container.view: 490 if self.params['esxi_hostname'] and isinstance(obj, vim.ClusterComputeResource) and hasattr(obj, 'host'): 491 for host in obj.host: 492 if host.name == resource_name: 493 return obj 494 495 if obj.name == resource_name: 496 return obj 497 498 return None 499 500 def _new_nic_spec(self, vm_obj, nic_obj=None, network_params=None): 501 network = self._get_network_object(vm_obj, network_params) 502 503 if network_params: 504 connected = network_params['connected'] 505 device_type = network_params['device_type'].lower() 506 directpath_io = network_params['directpath_io'] 507 guest_control = network_params['guest_control'] 508 label = network_params['label'] 509 mac_address = network_params['mac_address'] 510 start_connected = network_params['start_connected'] 511 wake_onlan = network_params['wake_onlan'] 512 else: 513 connected = self.params['connected'] 514 device_type = self.params['device_type'].lower() 515 directpath_io = self.params['directpath_io'] 516 guest_control = self.params['guest_control'] 517 label = self.params['label'] 518 mac_address = self.params['mac_address'] 519 start_connected = self.params['start_connected'] 520 wake_onlan = self.params['wake_onlan'] 521 522 if not nic_obj: 523 device_obj = self.nic_device_type[device_type] 524 nic_spec = vim.vm.device.VirtualDeviceSpec( 525 device=device_obj() 526 ) 527 if mac_address: 528 nic_spec.device.addressType = 'manual' 529 nic_spec.device.macAddress = mac_address 530 531 if label: 532 nic_spec.device.deviceInfo = vim.Description( 533 label=label 534 ) 535 else: 536 nic_spec = vim.vm.device.VirtualDeviceSpec( 537 operation=vim.vm.device.VirtualDeviceSpec.Operation.edit, 538 device=nic_obj 539 ) 540 if label and label != nic_obj.deviceInfo.label: 541 nic_spec.device.deviceInfo = vim.Description( 542 label=label 543 ) 544 if mac_address and mac_address != nic_obj.macAddress: 545 nic_spec.device.addressType = 'manual' 546 nic_spec.device.macAddress = mac_address 547 548 nic_spec.device.backing = self._nic_backing_from_obj(network) 549 nic_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo( 550 startConnected=start_connected, 551 allowGuestControl=guest_control, 552 connected=connected 553 ) 554 nic_spec.device.wakeOnLanEnabled = wake_onlan 555 556 if directpath_io and not isinstance(nic_spec.device, vim.vm.device.VirtualVmxnet3): 557 self.module.fail_json(msg='directpath_io can only be used with the vmxnet3 device type') 558 559 if directpath_io and isinstance(nic_spec.device, vim.vm.device.VirtualVmxnet3): 560 nic_spec.device.uptCompatibilityEnabled = True 561 return nic_spec 562 563 def _nic_backing_from_obj(self, network_obj): 564 rv = None 565 if isinstance(network_obj, vim.dvs.DistributedVirtualPortgroup): 566 rv = vim.VirtualEthernetCardDistributedVirtualPortBackingInfo( 567 port=vim.DistributedVirtualSwitchPortConnection( 568 portgroupKey=network_obj.key, 569 switchUuid=network_obj.config.distributedVirtualSwitch.uuid 570 ) 571 ) 572 elif isinstance(network_obj, vim.OpaqueNetwork): 573 rv = vim.vm.device.VirtualEthernetCard.OpaqueNetworkBackingInfo( 574 opaqueNetworkType='nsx.LogicalSwitch', 575 opaqueNetworkId=network_obj.summary.opaqueNetworkId 576 ) 577 elif isinstance(network_obj, vim.Network): 578 rv = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo( 579 deviceName=network_obj.name, 580 network=network_obj 581 ) 582 return rv 583 584 def _nic_absent(self, network_params=None): 585 changed = False 586 diff = {'before': {}, 'after': {}} 587 if network_params: 588 mac_address = network_params['mac_address'] 589 else: 590 mac_address = self.params['mac_address'] 591 592 device_spec = None 593 vm_obj = self.get_vm() 594 if not vm_obj: 595 self.module.fail_json(msg='could not find vm: {0}'.format(self.params['name'])) 596 nic_info, nic_obj_lst = self._get_nics_from_vm(vm_obj) 597 598 for nic in nic_info: 599 diff['before'].update({nic['mac_address']: copy.copy(nic)}) 600 601 network_info = copy.deepcopy(nic_info) 602 603 for nic_obj in nic_obj_lst: 604 if nic_obj.macAddress == mac_address: 605 if self.module.check_mode: 606 changed = True 607 for nic in nic_info: 608 if nic.get('mac_address') != nic_obj.macAddress: 609 diff['after'].update({nic['mac_address']: copy.copy(nic)}) 610 network_info = [nic for nic in nic_info if nic.get('mac_address') != nic_obj.macAddress] 611 return diff, changed, network_info 612 device_spec = vim.vm.device.VirtualDeviceSpec( 613 device=nic_obj, 614 operation=vim.vm.device.VirtualDeviceSpec.Operation.remove 615 ) 616 break 617 618 if not device_spec: 619 diff['after'] = diff['before'] 620 return diff, changed, network_info 621 622 try: 623 task = vm_obj.ReconfigVM_Task(vim.vm.ConfigSpec(deviceChange=[device_spec])) 624 wait_for_task(task) 625 except (vim.fault.InvalidDeviceSpec, vim.fault.RestrictedVersion) as e: 626 self.module.fail_json(msg='failed to reconfigure guest', detail=e.msg) 627 628 if task.info.state == 'error': 629 self.module.fail_json(msg='failed to reconfigure guest', detail=task.info.error.msg) 630 631 vm_obj = self.get_vm() 632 nic_info, nic_obj_lst = self._get_nics_from_vm(vm_obj) 633 634 for nic in nic_info: 635 diff['after'].update({nic.get('mac_address'): copy.copy(nic)}) 636 637 network_info = nic_info 638 if diff['after'] != diff['before']: 639 changed = True 640 641 return diff, changed, network_info 642 643 def _get_nic_info(self): 644 rv = {'network_info': []} 645 vm_obj = self.get_vm() 646 nic_info, nic_obj_lst = self._get_nics_from_vm(vm_obj) 647 648 rv['network_info'] = nic_info 649 return rv 650 651 def _deprectated_list_config(self): 652 ''' 653 this only exists to handle the old way of configuring interfaces, which 654 should be deprectated in favour of using loops in the playbook instead of 655 feeding lists directly into the module. 656 ''' 657 diff = {'before': {}, 'after': {}} 658 changed = False 659 for i in self.params['networks']: 660 network_params = {} 661 network_params['mac_address'] = i.get('mac') or i.get('manual_mac') 662 network_params['network_name'] = i.get('name') 663 network_params['vlan_id'] = i.get('vlan') 664 network_params['switch'] = i.get('dvswitch_name') 665 network_params['guest_control'] = i.get('allow_guest_control', self.params['guest_control']) 666 667 for k in ['connected', 'device_type', 'directpath_io', 'force', 'label', 'start_connected', 'state', 'wake_onlan']: 668 network_params[k] = i.get(k, self.params[k]) 669 670 if network_params['state'] in ['new', 'present']: 671 n_diff, n_changed, network_info = self._nic_present(network_params) 672 diff['before'].update(n_diff['before']) 673 diff['after'] = n_diff['after'] 674 if n_changed: 675 changed = True 676 677 if network_params['state'] == 'absent': 678 n_diff, n_changed, network_info = self._nic_absent(network_params) 679 diff['before'].update(n_diff['before']) 680 diff['after'] = n_diff['after'] 681 if n_changed: 682 changed = True 683 684 return diff, changed, network_info 685 686 def _nic_present(self, network_params=None): 687 changed = False 688 diff = {'before': {}, 'after': {}} 689 # backwards compatibility, clean up when params['networks'] 690 # has been removed 691 if network_params: 692 force = network_params['force'] 693 label = network_params['label'] 694 mac_address = network_params['mac_address'] 695 network_name = network_params['network_name'] 696 switch = network_params['switch'] 697 vlan_id = network_params['vlan_id'] 698 else: 699 force = self.params['force'] 700 label = self.params['label'] 701 mac_address = self.params['mac_address'] 702 network_name = self.params['network_name'] 703 switch = self.params['switch'] 704 vlan_id = self.params['vlan_id'] 705 706 vm_obj = self.get_vm() 707 if not vm_obj: 708 self.module.fail_json(msg='could not find vm: {0}'.format(self.params['name'])) 709 710 network_obj = self._get_network_object(vm_obj, network_params) 711 nic_info, nic_obj_lst = self._get_nics_from_vm(vm_obj) 712 label_lst = [d.get('label') for d in nic_info] 713 mac_addr_lst = [d.get('mac_address') for d in nic_info] 714 vlan_id_lst = [d.get('vlan_id') for d in nic_info] 715 network_name_lst = [d.get('network_name') for d in nic_info] 716 717 # TODO: make checks below less inelegant 718 if ((vlan_id in vlan_id_lst or network_name in network_name_lst) 719 and not mac_address 720 and not label 721 and not force): 722 for nic in nic_info: 723 diff['before'].update({nic.get('mac_address'): copy.copy(nic)}) 724 diff['after'].update({nic.get('mac_address'): copy.copy(nic)}) 725 return diff, changed, nic_info 726 727 if not network_obj and (network_name or vlan_id): 728 self.module.fail_json( 729 msg='unable to find specified network_name/vlan_id ({0}), check parameters'.format( 730 network_name or vlan_id 731 ) 732 ) 733 734 for nic in nic_info: 735 diff['before'].update({nic.get('mac_address'): copy.copy(nic)}) 736 737 if (mac_address and mac_address in mac_addr_lst) or (label and label in label_lst): 738 for nic_obj in nic_obj_lst: 739 if (mac_address and nic_obj.macAddress == mac_address) or (label and label == nic_obj.deviceInfo.label): 740 device_spec = self._new_nic_spec(vm_obj, nic_obj, network_params) 741 742 # fabricate diff for check_mode 743 if self.module.check_mode: 744 for nic in nic_info: 745 nic_mac = nic.get('mac_address') 746 nic_label = nic.get('label') 747 if nic_mac == mac_address or nic_label == label: 748 diff['after'][nic_mac] = copy.deepcopy(nic) 749 diff['after'][nic_mac].update({'switch': switch or nic['switch']}) 750 if network_obj: 751 diff['after'][nic_mac].update( 752 { 753 'vlan_id': self._get_vlanid_from_network(network_obj), 754 'network_name': network_obj.name 755 } 756 ) 757 else: 758 diff['after'].update({nic_mac: copy.deepcopy(nic)}) 759 760 if (not mac_address or mac_address not in mac_addr_lst) and (not label or label not in label_lst): 761 device_spec = self._new_nic_spec(vm_obj, None, network_params) 762 device_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add 763 if self.module.check_mode: 764 # fabricate diff/returns for checkmode 765 diff['after'] = copy.deepcopy(diff['before']) 766 nic_mac = mac_address 767 if not nic_mac: 768 nic_mac = 'AA:BB:CC:DD:EE:FF' 769 if not label: 770 label = 'check_mode_adapter' 771 diff['after'].update( 772 { 773 nic_mac: { 774 'vlan_id': self._get_vlanid_from_network(network_obj), 775 'network_name': network_obj.name, 776 'label': label, 777 'mac_address': nic_mac, 778 'unit_number': 40000 779 } 780 } 781 ) 782 783 if self.module.check_mode: 784 network_info = [diff['after'][i] for i in diff['after']] 785 if diff['after'] != diff['before']: 786 changed = True 787 return diff, changed, network_info 788 789 if not self.module.check_mode: 790 try: 791 task = vm_obj.ReconfigVM_Task(vim.vm.ConfigSpec(deviceChange=[device_spec])) 792 wait_for_task(task) 793 except (vim.fault.InvalidDeviceSpec, vim.fault.RestrictedVersion) as e: 794 self.module.fail_json(msg='failed to reconfigure guest', detail=e.msg) 795 796 if task.info.state == 'error': 797 self.module.fail_json(msg='failed to reconfigure guest', detail=task.info.error.msg) 798 799 vm_obj = self.get_vm() 800 network_info, nic_obj_lst = self._get_nics_from_vm(vm_obj) 801 for nic in network_info: 802 diff['after'].update({nic.get('mac_address'): copy.copy(nic)}) 803 804 if diff['after'] != diff['before']: 805 changed = True 806 return diff, changed, network_info 807 808 809def main(): 810 argument_spec = vmware_argument_spec() 811 argument_spec.update( 812 name=dict(type='str'), 813 uuid=dict(type='str'), 814 use_instance_uuid=dict(type='bool', default=False), 815 moid=dict(type='str'), 816 folder=dict(type='str'), 817 datacenter=dict(type='str', default='ha-datacenter'), 818 esxi_hostname=dict(type='str'), 819 cluster=dict(type='str'), 820 mac_address=dict(type='str'), 821 vlan_id=dict(type='int'), 822 network_name=dict(type='str'), 823 device_type=dict(type='str', default='vmxnet3'), 824 label=dict(type='str'), 825 switch=dict(type='str'), 826 connected=dict(type='bool', default=True), 827 start_connected=dict(type='bool', default=True), 828 wake_onlan=dict(type='bool', default=False), 829 directpath_io=dict(type='bool', default=False), 830 force=dict(type='bool', default=False), 831 gather_network_info=dict(type='bool', default=False, aliases=['gather_network_facts']), 832 networks=dict(type='list', default=[], elements='dict'), 833 guest_control=dict(type='bool', default=True), 834 state=dict(type='str', default='present', choices=['absent', 'present']) 835 ) 836 837 module = AnsibleModule( 838 argument_spec=argument_spec, 839 mutually_exclusive=[ 840 ['vlan_id', 'network_name'] 841 ], 842 required_one_of=[ 843 ['name', 'uuid', 'moid'] 844 ], 845 supports_check_mode=True 846 ) 847 848 pyv = PyVmomiHelper(module) 849 850 if module.params['gather_network_info']: 851 nics = pyv._get_nic_info() 852 network_data = {} 853 nics_sorted = sorted(nics.get('network_info'), key=lambda k: k['unit_number']) 854 for n, i in enumerate(nics_sorted): 855 key_name = '{0}'.format(n) 856 network_data[key_name] = i 857 network_data[key_name].update({'mac_addr': i['mac_address'], 'name': i['network_name']}) 858 859 module.exit_json(network_info=nics.get('network_info'), network_data=network_data, changed=False) 860 861 if module.params['networks']: 862 network_data = {} 863 module.deprecate( 864 msg='The old way of configuring interfaces by supplying an arbitrary list will be removed, loops should be used to handle multiple interfaces', 865 version='2.0.0', 866 collection_name='community.vmware' 867 ) 868 diff, changed, network_info = pyv._deprectated_list_config() 869 nd = copy.deepcopy(network_info) 870 nics_sorted = sorted(nd, key=lambda k: k['unit_number']) 871 for n, i in enumerate(nics_sorted): 872 key_name = '{0}'.format(n) 873 network_data[key_name] = i 874 network_data[key_name].update({'mac_addr': i['mac_address'], 'name': i['network_name']}) 875 876 module.exit_json(changed=changed, network_info=network_info, network_data=network_data, diff=diff) 877 878 if module.params['state'] == 'present': 879 diff, changed, network_info = pyv._nic_present() 880 881 if module.params['state'] == 'absent': 882 if not module.params['mac_address']: 883 module.fail_json(msg='parameter mac_address required when removing nics') 884 diff, changed, network_info = pyv._nic_absent() 885 886 module.exit_json(changed=changed, network_info=network_info, diff=diff) 887 888 889if __name__ == '__main__': 890 main() 891