| #!/usr/bin/env python3 | |
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| from pyVim.connect import SmartConnect, SmartConnectNoSSL, Disconnect | |
| from pyVmomi import vim | |
| import atexit | |
| import sys | |
| import argparse | |
| import json | |
| isDebugLogs = False | |
| hostClusterNameDict = {} | |
| pgHostNameDict = {} | |
| networksDict = {} | |
| def log_message(msg): | |
| if isDebugLogs == True: | |
| print(msg) | |
| def get_clusters(content, cluster=None): | |
| if cluster is not None: | |
| log_message("Getting clusters (name=" + cluster + ") ...") | |
| else: | |
| log_message("Getting clusters ...") | |
| cluster_view = content.viewManager.CreateContainerView(content.rootFolder, | |
| [vim.ClusterComputeResource], | |
| True) | |
| clusters = [] | |
| if cluster is not None: | |
| for c in cluster_view.view: | |
| if c.name == cluster: | |
| clusters.append(c) | |
| hosts = c.host | |
| for host in hosts: | |
| hostClusterNameDict[host.name] = c.name | |
| break | |
| else: | |
| for c in cluster_view.view: | |
| clusters.append(c) | |
| hosts = c.host | |
| for host in hosts: | |
| hostClusterNameDict[host.name] = c.name | |
| cluster_view.Destroy() | |
| log_message('\t{} cluster(s) found'.format(len(clusters))) | |
| for c in clusters: | |
| log_message('\t' + c.name) | |
| return clusters | |
| def get_vm_hosts(clusters): | |
| log_message("Getting ESX hosts ...") | |
| hosts = [] | |
| for cluster in clusters: | |
| hosts.extend(cluster.host) | |
| log_message('\t{} host(s) found'.format(len(hosts))) | |
| for host in hosts: | |
| log_message('\t' + host.name) | |
| return hosts | |
| def get_vms(content): | |
| log_message("Getting VMs ...") | |
| vm_view = content.viewManager.CreateContainerView(content.rootFolder, | |
| [vim.VirtualMachine], | |
| True) | |
| obj = [vm for vm in vm_view.view] | |
| vm_view.Destroy() | |
| return obj | |
| def get_hosts_port_groups(hosts): | |
| log_message("Collecting portGroups on hosts. This may take a while ...") | |
| hostPgDict = {} | |
| for host in hosts: | |
| pgs = host.config.network.portgroup | |
| hostPgDict[host] = pgs | |
| for pg in pgs: | |
| pgHostNameDict[pg.spec.name] = host.name | |
| log_message("\tHost {} done.".format(host.name)) | |
| log_message("\tPortgroup collection complete.") | |
| return hostPgDict | |
| def get_vm_info(vm, hostPgDict): | |
| vmPowerState = vm.runtime.powerState | |
| log_message('\tVM: ' + vm.name + '(' + vmPowerState + ')') | |
| get_vm_nics(vm, hostPgDict) | |
| def get_vm_nics(vm, hostPgDict): | |
| try: | |
| for dev in vm.config.hardware.device: | |
| if isinstance(dev, vim.vm.device.VirtualEthernetCard): | |
| dev_backing = dev.backing | |
| portGroup = None | |
| vlanId = None | |
| isolatedPvlan = None | |
| isolatedPvlanType = None | |
| vSwitch = None | |
| if hasattr(dev_backing, 'port'): | |
| portGroupKey = dev.backing.port.portgroupKey | |
| dvsUuid = dev.backing.port.switchUuid | |
| try: | |
| dvs = content.dvSwitchManager.QueryDvsByUuid(dvsUuid) | |
| except: | |
| log_message('\tError: Unable retrieve details for distributed vSwitch ' + dvsUuid) | |
| portGroup = '' | |
| vlanId = '' | |
| vSwitch = '' | |
| else: | |
| pgObj = dvs.LookupDvPortGroup(portGroupKey) | |
| portGroup = pgObj.config.name | |
| try: | |
| if isinstance(pgObj.config.defaultPortConfig.vlan, vim.dvs.VmwareDistributedVirtualSwitch.PvlanSpec): | |
| for pvlanConfig in dvs.config.pvlanConfig: | |
| if pvlanConfig.secondaryVlanId == pgObj.config.defaultPortConfig.vlan.pvlanId: | |
| vlanId = str(pvlanConfig.primaryVlanId) | |
| isolatedPvlanType = pvlanConfig.pvlanType | |
| isolatedPvlan = str(pgObj.config.defaultPortConfig.vlan.pvlanId) | |
| break | |
| else: | |
| vlanId = str(pgObj.config.defaultPortConfig.vlan.vlanId) | |
| except AttributeError: | |
| log_message('\tError: Unable retrieve details for portgroup ' + portGroup) | |
| vlanId = '' | |
| vSwitch = str(dvs.name) | |
| else: | |
| portGroup = dev.backing.network.name | |
| vmHost = vm.runtime.host | |
| # global variable hostPgDict stores portGroups per host | |
| pgs = hostPgDict[vmHost] | |
| for p in pgs: | |
| if portGroup in p.key: | |
| vlanId = str(p.spec.vlanId) | |
| vSwitch = str(p.spec.vswitchName) | |
| if portGroup is None: | |
| portGroup = '' | |
| if vlanId is None: | |
| vlanId = '' | |
| vmHostName = None | |
| vmClusterName = None | |
| try: | |
| vmHostName = vm.runtime.host.name | |
| except AttributeError: | |
| vmHostName = '' | |
| try: | |
| vmClusterName = vm.runtime.host.parent.name | |
| except AttributeError: | |
| vmClusterName = '' | |
| add_network(portGroup, vlanId, isolatedPvlanType, isolatedPvlan, vSwitch, vm.name, dev.deviceInfo.label, dev.macAddress, vmClusterName, vmHostName) | |
| log_message('\t\t' + dev.deviceInfo.label + '->' + dev.macAddress + | |
| ' @ ' + vSwitch + '->' + portGroup + | |
| ' (VLAN ' + vlanId + ')') | |
| except AttributeError: | |
| log_message('\tError: Unable retrieve details for ' + vm.name) | |
| def add_network(portGroup, vlanId, isolatedPvlanType, isolatedPvlan, vSwitch, vmName, vmDeviceLabel, vmMacAddress, vmClusterName, vmHostName): | |
| key = vSwitch + '->' + portGroup + ' (VLAN ' + vlanId + ')' | |
| device = {"label": vmDeviceLabel, "macaddress": vmMacAddress} | |
| vm = {"name":vmName, "device": device} | |
| if key in networksDict: | |
| network = networksDict[key] | |
| network["virtualmachines"].append(vm) | |
| networksDict[key] = network | |
| else: | |
| vms = [vm] | |
| try: | |
| host = pgHostNameDict[portGroup] | |
| except KeyError: | |
| host = vmHostName | |
| try: | |
| cluster = hostClusterNameDict[host] | |
| except KeyError: | |
| cluster = vmClusterName | |
| network = {"portgroup": portGroup, "cluster": cluster, "host": host, "switch": vSwitch, "virtualmachines": vms} | |
| if vlanId != '': | |
| network["vlanid"] = vlanId | |
| if isolatedPvlan is not None: | |
| network["isolatedpvlan"] = isolatedPvlan | |
| if isolatedPvlanType is not None: | |
| network["isolatedpvlantype"] = isolatedPvlanType | |
| networksDict[key] = network | |
| def get_args(): | |
| parser = argparse.ArgumentParser( | |
| description='Arguments for talking to vCenter') | |
| parser.add_argument('-s', '--host', | |
| required=True, | |
| action='store', | |
| help='vSpehre service to connect to') | |
| parser.add_argument('-o', '--port', | |
| type=int, | |
| default=443, | |
| action='store', | |
| help='Port to connect on') | |
| parser.add_argument('-u', '--user', | |
| required=True, | |
| action='store', | |
| help='User name to use') | |
| parser.add_argument('-p', '--password', | |
| required=False, | |
| action='store', | |
| help='Password to use') | |
| parser.add_argument('-c', '--cluster', | |
| required=False, | |
| action='store', | |
| help='Cluster for which discover networks') | |
| parser.add_argument('-S', '--disable_ssl_verification', | |
| required=False, | |
| action='store_true', | |
| help='Disable ssl host certificate verification') | |
| parser.add_argument('-d', '--debug', | |
| required=False, | |
| action='store_true', | |
| help='Debug log messages') | |
| args = parser.parse_args() | |
| return args | |
| def main(): | |
| global content, isDebugLogs, hostClusterNameDict, pgHostNameDict, networksDict | |
| args = get_args() | |
| if args.password: | |
| password = args.password | |
| else: | |
| password = getpass.getpass(prompt='Enter password for host %s and ' | |
| 'user %s: ' % (args.host, args.user)) | |
| if args.debug: | |
| isDebugLogs = True | |
| if args.disable_ssl_verification: | |
| serviceInstance = SmartConnectNoSSL(host=args.host, | |
| user=args.user, | |
| pwd=password, | |
| port=int(args.port)) | |
| else: | |
| serviceInstance = SmartConnect(host=args.host, | |
| user=args.user, | |
| pwd=password, | |
| port=int(args.port)) | |
| atexit.register(Disconnect, serviceInstance) | |
| content = serviceInstance.RetrieveContent() | |
| if args.cluster: | |
| clusters = get_clusters(content, args.cluster) | |
| else: | |
| clusters = get_clusters(content) | |
| hosts = [] | |
| if len(clusters) > 0: | |
| hosts = get_vm_hosts(clusters) | |
| if len(hosts) > 0: | |
| hostPgDict = get_hosts_port_groups(hosts) | |
| vms = get_vms(content) | |
| log_message('\t{} VM(s) found'.format(len(vms))) | |
| for vm in vms: | |
| get_vm_info(vm, hostPgDict) | |
| networks = list(networksDict.values()) | |
| response = {"count": len(networks), "networks": networks} | |
| print(json.dumps(response, indent=2, sort_keys=True)) | |
| # Main section | |
| if __name__ == "__main__": | |
| sys.exit(main()) |