├── LICENSE.md ├── README.md ├── docs ├── multi-clone.md ├── pysphere-multi-clone.md └── random-vmotion.md ├── fetch-host-mor.py ├── multi-clone.py ├── pysphere-get-vm-ips.py ├── pysphere-multi-clone.py └── random-vmotion.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2020, Philippe Dellaert 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. Neither the name of Philippe Dellaert nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY PHILIPPE DELLAERT ''AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL PHILIPPE DELLAERT BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vSphere-Python 2 | ============== 3 | 4 | Collection of Python vSphere scripts 5 | 6 | # multi-clone.py # 7 | multi-clone is a Python script which allows you to clone a virtual machine or virtual machine template into multiple new virtual machines in a VMware vSphere environment. 8 | 9 | This script has the following capabilities: 10 | * Deploy a specified amount of virtual machines 11 | * Deploy in a specified folder 12 | * Deploy in a specified resource pool 13 | * Set advanced configuration options 14 | * Specify if the cloned virtual machines need to be powered on 15 | * Print out information of the main network interface (mac and ip, either IPv4 or IPv6) 16 | * Run a post-processing script with 3 parameters (virtual machine name, mac and ip) 17 | * Instead of setting the basename, amount, resource pool and folder a CSV can be used 18 | * Print logging to a log file or stdout 19 | * Do this in a threaded way 20 | 21 | Check [the multi-clone.py documentation](https://github.com/pdellaert/vSphere-Python/blob/master/docs/multi-clone.md) for more information on the options and capabilities. 22 | 23 | # random-vmotion.py # 24 | random-vmotion is a Python script which will vMotion VMs randomly to a set of hosts until stopped by a keyboard interupt (ctrl-c) 25 | 26 | This script has the following capabilities: 27 | * vMotion VMs to a random host 28 | * Continue until stopped 29 | * Print logging to a log file or stdout 30 | * Do this threaded 31 | 32 | Check [the random-vmotion.py documentation](https://github.com/pdellaert/vSphere-Python/blob/master/docs/random-vmotion.md) for more information on the options and capabilities. 33 | 34 | # fetch-host-mor.py # 35 | fetch-host-mor is a Python script which will provide the MOR details of one or all ESXi hosts in a vCenter environment. 36 | 37 | This script has the following capabilities: 38 | * Print out the Name, HW UUID and MOR for one or all ESXi hosts in a vCenter server. 39 | * Print as a nice table, or as JSON 40 | 41 | # pysphere-multi-clone.py # 42 | This script can be used to deploy multiple VMs from a template in an automatic way, with the possibility to add a post script. The post script gets two parameters: the VM name and possibly the IP address (either IPv4 or IPv6, depending on the parameters) 43 | 44 | Check [the pysphere-multi-clone.py documentation](https://github.com/pdellaert/vSphere-Python/blob/master/docs/pysphere-multi-clone.md) for more information on the options and capabilities. 45 | 46 | Contributing 47 | ============ 48 | 1. Fork the repository on Github 49 | 2. Create a named feature branch 50 | 3. Write your change 51 | 5. Submit a Pull Request using Github 52 | -------------------------------------------------------------------------------- /docs/multi-clone.md: -------------------------------------------------------------------------------- 1 | multi-clone.py 2 | ============== 3 | multi-clone is a Python script which allows you to clone a virtual machine or virtual machine template into multiple new virtual machines in a VMware vSphere environment. 4 | 5 | This script has the following capabilities: 6 | * Deploy a specified amount of virtual machines 7 | * Deploy in a specified datacenter 8 | * Deploy in a specified cluster 9 | * Deploy in a specified datastore 10 | * Deploy in a specified folder 11 | * Deploy in a specified resource pool 12 | * Set advanced configuration options 13 | * Specify if the cloned virtual machines need to be powered on 14 | * Print out information of the main network interface (mac and ip, either IPv4 or IPv6) 15 | * Run a post-processing script with 3 parameters (virtual machine name, mac and ip) 16 | * Instead of setting the basename, amount, resource pool and folder a CSV can be used 17 | * Print logging to a log file or stdout 18 | * Do this in a threaded way 19 | * Use Linked Clones to speed up cloning 20 | 21 | ### Using threads ### 22 | Deciding on the optimal amount of threads might need a bit of experimentation. Keep certain things in mind: 23 | * The optimal amount of threads depends on the IOPS of the datastore as each thread will start a template deployment task, which in turn starts copying the disks. 24 | * vCenter will, by default, only run 8 deployment tasks simultaniously while other tasks are queued, so setting the amount of threads to more than 8, is not really usefull. 25 | 26 | ### Using CSV file ### 27 | A CSV file can be provided with a line for each VM that needs to be created, with specific parameters for each VM. The format of each row should be (fields surrounded without [] are mandatory, fields surrounded with [] are optional): 28 | ``` 29 | "";"[Datacenter]";"[Cluster]";"[Resouce Pool]";"[Folder]";"[Datastore]";"[MAC Address]";"[Post-processing Script]";"[Advanced VM Parameters in JSON format]" 30 | ``` 31 | For instance: 32 | ``` 33 | "Test01";"New-York";"Compute-Cluster-01";"Development";"IT";"VSAN-DS";"00:50:56:11:11:11";"run.sh";"{""parameter.1"":""value.1"",""parameter.2"":""value.2""}" 34 | ``` 35 | ### Post-processing Script ### 36 | The Post-processing script is run for each VM created if it is provided either as a commandline parameter or as a field in the CSV. 37 | It is run with the following parameters: 38 | * virtual machine name, mac and ip : If Print IPs or Print MACs is enabled, combined with Power on 39 | * virtual machine name, mac: If a custom mac address was specified (even if VM is not powered on) 40 | * virtual machine name: If a power on is disabled and no custom mac address is enabled 41 | 42 | ### Usage ### 43 | usage: multi-clone.py [-h] [-6] [-b BASENAME] [-c COUNT] [-C CSVFILE] 44 | [--cluster CLUSTER] [-d] [--datacenter DATACENTER] 45 | [--datastore DATASTORE] [--folder FOLDER] -H HOST [-i] 46 | [-m] [-l LOGFILE] [-L] [--snapshot SNAPSHOT] [-n AMOUNT] 47 | [-o PORT] [-p PASSWORD] [-P] 48 | [--resource-pool RESOURCE_POOL] [-s POST_SCRIPT] [-S] -t 49 | TEMPLATE [-T THREADS] -u USERNAME [-v] [-w MAXWAIT] 50 | 51 | Deploy a template into multiple VM's. You can get information returned with 52 | the name of the virtual machine created and it's main mac and ip address. 53 | Either in IPv4 or IPv6 format. You can specify which folder and/or resource 54 | pool the clone should be placed in. Verbose and debug output can either be 55 | send to stdout, or saved to a log file. A post-script can be specified for 56 | post-processing. And it can all be done in a number of parallel threads you 57 | specify. The script also provides the ability to use a CSV for a lot of it 58 | settings and if you want to specify the mac address of the clones (usefull for 59 | DHCP/PXE configuration). 60 | 61 | optional arguments: 62 | -h, --help show this help message and exit 63 | -6, --six Get IPv6 address for VMs instead of IPv4 64 | -b BASENAME, --basename BASENAME 65 | Basename of the newly deployed VMs 66 | -c COUNT, --count COUNT 67 | Starting count, the name of the first VM deployed will 68 | be -, the second will be 69 | - (default = 1) 70 | -C CSVFILE, --csv CSVFILE 71 | An optional CSV overwritting the basename and count. 72 | For each line, a clone will be created. A line consits 73 | of the following fields, fields inside <> are 74 | mandatory, fields with [] are not: "";"[Datacenter]";"[Cluster]";"[Resouce 76 | Pool]";"[Folder]";"[Datastore]";"[MAC Address 77 | ]";"[Post-processing Script]";"[Advanced VM Parameters 78 | in JSON format]" 79 | --cluster CLUSTER The cluster in which the new VMs should reside 80 | (default = same cluster as source virtual machine) 81 | -d, --debug Enable debug output 82 | --datacenter DATACENTER 83 | The datacenter in which the new VMs should reside 84 | (default = same datacenter as source virtual machine) 85 | --datastore DATASTORE 86 | The datastore in which the new VMs should reside 87 | (default = same datastore as source virtual machine) 88 | --folder FOLDER The folder in which the new VMs should reside (default 89 | = same folder as source virtual machine) 90 | -H HOST, --host HOST The vCenter or ESXi host to connect to 91 | -i, --print-ips Enable IP output 92 | -m, --print-macs Enable MAC output 93 | -l LOGFILE, --log-file LOGFILE 94 | File to log to (default = stdout) 95 | -L, --linked Enable linked cloning 96 | --snapshot SNAPSHOT Snapshot to be used for linked cloning 97 | -n AMOUNT, --number AMOUNT 98 | Amount of VMs to deploy (default = 1) 99 | -o PORT, --port PORT Server port to connect to (default = 443) 100 | -p PASSWORD, --password PASSWORD 101 | The password with which to connect to the host. If not 102 | specified, the user is prompted at runtime for a 103 | password 104 | -P, --disable-power-on 105 | Disable power on of cloned VMs 106 | --resource-pool RESOURCE_POOL 107 | The resource pool in which the new VMs should reside, 108 | (default = Resources, the root resource pool) 109 | -s POST_SCRIPT, --post-script POST_SCRIPT 110 | Script to be called after each VM is created and 111 | booted. Arguments passed: name mac-address ip-address 112 | -S, --disable-SSL-certificate-verification 113 | Disable SSL certificate verification on connect 114 | -t TEMPLATE, --template TEMPLATE 115 | Template to deploy 116 | -T THREADS, --threads THREADS 117 | Amount of threads to use. Choose the amount of threads 118 | with the speed of your datastore in mind, each thread 119 | starts the creation of a virtual machine. (default = 120 | 1) 121 | -u USERNAME, --user USERNAME 122 | The username with which to connect to the host 123 | -v, --verbose Enable verbose output 124 | -w MAXWAIT, --wait-max MAXWAIT 125 | Maximum amount of seconds to wait when gathering 126 | information (default = 120) 127 | 128 | ### Issues and feature requests ### 129 | Feel free to use the [Github issue tracker](https://github.com/pdellaert/vSphere-Python/issues) of the repository to post issues and feature requests 130 | 131 | ### Requirements ### 132 | 1. [pyVmomi](https://github.com/vmware/pyvmomi) 133 | 2. vCenter 5+ (tested with 5.1, 5.1u, 5.5 & 6.0) 134 | 3. A user with a role with at least the following permission over the complete vCenter server: 135 | * Datastore 136 | * Allocate space 137 | * Network 138 | * Assign Network 139 | * Resource 140 | * Apply recommendation 141 | * Assign virtual machine to resource pool 142 | * Scheduled task 143 | * Create tasks 144 | * Run task 145 | * Virtual Machine 146 | * Configuration 147 | * Add new disk 148 | * Interaction 149 | * Power on 150 | * Inventory 151 | * Create from existing 152 | * Provisioning 153 | * Clone virtual machine 154 | * Deploy from template 155 | 156 | -------------------------------------------------------------------------------- /docs/pysphere-multi-clone.md: -------------------------------------------------------------------------------- 1 | pysphere-multi-clone.py 2 | ======================= 3 | This script can be used to deploy multiple VMs from a template in an automatic way, with the possibility to add a post script. The post script gets two parameters: the VM name and possibly the IP address (either IPv4 or IPv6, depending on the parameters) 4 | 5 | ### Requirements ### 6 | 1. [PySphere 0.1.8+](https://code.google.com/p/pysphere/) 7 | 2. vCenter 5+ (tested with 5.1, 5.1u & 5.5) 8 | 3. A user with a role with at least the following permission over the complete vCenter server: 9 | * Datastore 10 | * Allocate space 11 | * Network 12 | * Assign Network 13 | * Resource 14 | * Apply recommendation 15 | * Assign virtual machine to resource pool 16 | * Scheduled task 17 | * Create tasks 18 | * Run task 19 | * Virtual Machine 20 | * Configuration 21 | * Add new disk 22 | * Interaction 23 | * Power on 24 | * Inventory 25 | * Create from existing 26 | * Provisioning 27 | * Clone virtual machine 28 | * Deploy from template 29 | 30 | ### Usage ### 31 | multi-clone.py [-h] [-6] -b BASENAME [-c COUNT] [-n AMOUNT] 32 | [-p POST_SCRIPT] [-r RESOURCE_POOL] -s SERVER -t 33 | TEMPLATE -u USERNAME [-v] [-w MAXWAIT] 34 | 35 | Deploy a template into multiple VM's 36 | 37 | optional arguments: 38 | -h, --help show this help message and exit 39 | -6, --six Get IPv6 address for VMs instead of IPv4 40 | -b BASENAME, --basename BASENAME 41 | Basename of the newly deployed VMs 42 | -c COUNT, --count COUNT 43 | Starting count, the name of the first VM deployed will 44 | be -, the second will be 45 | - (default=1) 46 | -n AMOUNT, --number AMOUNT 47 | Amount of VMs to deploy (default=1) 48 | -p POST_SCRIPT, --post-script POST_SCRIPT 49 | Script to be called after each VM is created and 50 | booted. Arguments passed: name ip-address 51 | -r RESOURCE_POOL, --resource-pool RESOURCE_POOL 52 | The resource pool in which the new VMs should reside 53 | -s SERVER, --server SERVER 54 | The vCenter or ESXi server to connect to 55 | -t TEMPLATE, --template TEMPLATE 56 | Template to deploy 57 | -u USERNAME, --user USERNAME 58 | The username with which to connect to the server 59 | -v, --verbose Enable verbose output 60 | -w MAXWAIT, --wait-max MAXWAIT 61 | Maximum amount of seconds to wait when gathering 62 | information (default 120) 63 | -------------------------------------------------------------------------------- /docs/random-vmotion.md: -------------------------------------------------------------------------------- 1 | random-vmotion.py 2 | ================= 3 | random-vmotion is a Python script which will vMotion VMs randomly to a set of hosts until stopped by a keyboard interupt (ctrl-c) 4 | 5 | This script has the following capabilities: 6 | * vMotion VMs to a random host 7 | * Continue until stopped 8 | * Print logging to a log file or stdout 9 | * Do this threaded 10 | 11 | ### Using threads ### 12 | Deciding on the optimal amount of threads might need a bit of experimentation. Keep certain things in mind: 13 | * The optimal amount of threads depends on the memory consumption of the VMs, the activity of the VMs and the amount of hosts as each thread will execute a vMotion task. If this is all to the same host with the a lot of activity in the VM, you might get in trouble. 14 | 15 | ### Files ### 16 | The files are a list of VMs and Hosts, each in a seperate file and with one entry per line 17 | 18 | ### Usage ### 19 | usage: random-vmotion.py [-h] [-d] -H HOST [-i INTERVAL] [-l LOGFILE] 20 | [-o PORT] [-p PASSWORD] [-S] -t TARGETFILE 21 | [-T THREADS] -u USERNAME [-v] -V VMFILE 22 | 23 | Randomly vMotion each VM from a list one by one to a random host from a list, 24 | until stopped. 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | -d, --debug Enable debug output 29 | -H HOST, --host HOST The vCenter or ESXi host to connect to 30 | -i INTERVAL, --interval INTERVAL 31 | The amount of time to wait after a vMotion is finished 32 | to schedule a new one (default 30 seconds) 33 | -l LOGFILE, --log-file LOGFILE 34 | File to log to (default = stdout) 35 | -o PORT, --port PORT Server port to connect to (default = 443) 36 | -p PASSWORD, --password PASSWORD 37 | The password with which to connect to the host. If not 38 | specified, the user is prompted at runtime for a 39 | password 40 | -S, --disable-SSL-certificate-verification 41 | Disable SSL certificate verification on connect 42 | -t TARGETFILE, --targets TARGETFILE 43 | File with the list of target hosts to vMotion to 44 | -T THREADS, --threads THREADS 45 | Amount of simultanious vMotions to execute at once. 46 | (default = 1) 47 | -u USERNAME, --user USERNAME 48 | The username with which to connect to the host 49 | -v, --verbose Enable verbose output 50 | -V VMFILE, --vms VMFILE 51 | File with the list of VMs to vMotion 52 | 53 | ### Issues and feature requests ### 54 | Feel free to use the [Github issue tracker](https://github.com/pdellaert/vSphere-Python/issues) of the repository to post issues and feature requests 55 | 56 | ### Requirements ### 57 | 1. [pyVmomi](https://github.com/vmware/pyvmomi) 58 | 2. vCenter 5+ (tested with 5.1, 5.1u, 5.5 & 6.0) -------------------------------------------------------------------------------- /fetch-host-mor.py: -------------------------------------------------------------------------------- 1 | """ 2 | fetch-host-mor is a Python script which will provide the MOR details of one or all ESXi hosts in a vCenter environment. 3 | 4 | --- Usage --- 5 | Run 'fetch-host-mor.py -h' for an overview 6 | 7 | --- Author --- 8 | Philippe Dellaert 9 | 10 | --- License --- 11 | https://raw.github.com/pdellaert/vSphere-Python/master/LICENSE.md 12 | 13 | """ 14 | 15 | from builtins import str 16 | import argparse 17 | import atexit 18 | import json 19 | import getpass 20 | import logging 21 | 22 | from prettytable import PrettyTable 23 | from pyVim.connect import SmartConnect, SmartConnectNoSSL, Disconnect 24 | from pyVmomi import vim, vmodl 25 | 26 | 27 | def get_args(): 28 | """ 29 | Supports the command-line arguments listed below. 30 | """ 31 | 32 | parser = argparse.ArgumentParser(description="Randomly vMotion each VM from a list one by one to a random host from a list, until stopped.") 33 | parser.add_argument('-d', '--debug', required=False, help='Enable debug output', dest='debug', action='store_true') 34 | parser.add_argument('-H', '--host', nargs=1, required=False, help='The host for which to return the MOR details, if not provided, provides MOR details for all ESXi hosts', dest='host', type=str) 35 | parser.add_argument('-j', '--json', required=False, help='Print as JSON, not as a table', dest='json_output', action='store_true') 36 | parser.add_argument('-l', '--log-file', nargs=1, required=False, help='File to log to (default = stdout)', dest='logfile', type=str) 37 | parser.add_argument('-o', '--port', nargs=1, required=False, help='Server port to connect to (default = 443)', dest='port', type=int, default=[443]) 38 | parser.add_argument('-p', '--password', nargs=1, required=False, help='The password with which to connect to the host. If not specified, the user is prompted at runtime for a password', dest='password', type=str) 39 | parser.add_argument('-S', '--disable-SSL-certificate-verification', required=False, help='Disable SSL certificate verification on connect', dest='nosslcheck', action='store_true') 40 | parser.add_argument('-u', '--user', nargs=1, required=True, help='The username with which to connect to the host', dest='username', type=str) 41 | parser.add_argument('-v', '--verbose', required=False, help='Enable verbose output', dest='verbose', action='store_true') 42 | parser.add_argument('-V', '--vcenter', nargs=1, required=True, help='The vCenter or ESXi host to connect to', dest='vcenter', type=str) 43 | args = parser.parse_args() 44 | return args 45 | 46 | 47 | def main(): 48 | """ 49 | Find one or all ESXi hosts and print the MOR information 50 | """ 51 | 52 | # Handling arguments 53 | args = get_args() 54 | debug = args.debug 55 | host = None 56 | if args.host: 57 | host = args.host[0] 58 | json_output = args.json_output 59 | log_file = None 60 | if args.logfile: 61 | log_file = args.logfile[0] 62 | port = args.port[0] 63 | password = None 64 | if args.password: 65 | password = args.password[0] 66 | nosslcheck = args.nosslcheck 67 | username = args.username[0] 68 | verbose = args.verbose 69 | vcenter = args.vcenter[0] 70 | 71 | # Logging settings 72 | if debug: 73 | log_level = logging.DEBUG 74 | elif verbose: 75 | log_level = logging.INFO 76 | else: 77 | log_level = logging.WARNING 78 | 79 | if log_file: 80 | logging.basicConfig(filename=log_file, format='%(asctime)s %(levelname)s %(message)s', level=log_level) 81 | else: 82 | logging.basicConfig(filename=log_file, format='%(asctime)s %(levelname)s %(message)s', level=log_level) 83 | logger = logging.getLogger(__name__) 84 | 85 | if json_output: 86 | logger.debug('Setting up json output') 87 | json_object = [] 88 | else: 89 | logger.debug('Setting up basic output table') 90 | pt = PrettyTable(['Name', 'MOR value', 'HW UUID']) 91 | 92 | # Getting user password 93 | if password is None: 94 | logger.debug('No command line password received, requesting password from user') 95 | password = getpass.getpass(prompt='Enter password for vCenter %s for user %s: ' % (host, username)) 96 | 97 | try: 98 | si = None 99 | try: 100 | logger.info('Connecting to server %s:%s with username %s' % (vcenter, port, username)) 101 | if nosslcheck: 102 | si = SmartConnectNoSSL(host=vcenter, user=username, pwd=password, port=int(port)) 103 | else: 104 | si = SmartConnect(host=vcenter, user=username, pwd=password, port=int(port)) 105 | except IOError as e: 106 | pass 107 | 108 | if not si: 109 | logger.error('Could not connect to host %s with user %s and specified password' % (vcenter, username)) 110 | return 1 111 | 112 | logger.debug('Registering disconnect at exit') 113 | atexit.register(Disconnect, si) 114 | 115 | # Getting hosts 116 | content = si.content 117 | obj_view = content.viewManager.CreateContainerView(content.rootFolder, [vim.HostSystem], True) 118 | esxi_host_list = obj_view.view 119 | esxi_hosts = [] 120 | found_host = False 121 | for esxi_host in esxi_host_list: 122 | logger.debug('Found Host %s' % esxi_host.name) 123 | if host == None: 124 | esxi_hosts.append(esxi_host) 125 | elif esxi_host.name == host: 126 | esxi_hosts.append(esxi_host) 127 | found_host = True 128 | break 129 | 130 | if host != None and not found_host: 131 | logger.error('Host %s does not exist' % host) 132 | 133 | for esxi_host in esxi_hosts: 134 | esxi_host_name = esxi_host.name 135 | esxi_host_mor = str(esxi_host).split(':')[1].replace("'", '') 136 | esxi_host_hw_uuid = esxi_host.summary.hardware.uuid 137 | logger.debug('name: %s, mor: %s, hw uuid: %s' % (esxi_host_name, esxi_host_mor, esxi_host_hw_uuid)) 138 | if json_output: 139 | json_dict = { 140 | 'Name': esxi_host_name, 141 | 'MOR value': esxi_host_mor, 142 | 'HW UUID': esxi_host_hw_uuid 143 | } 144 | json_object.append(json_dict) 145 | else: 146 | pt.add_row([esxi_host_name, esxi_host_mor, esxi_host_hw_uuid]) 147 | 148 | if json_output: 149 | print(json.dumps(json_object, sort_keys=True, indent=4)) 150 | else: 151 | print(pt) 152 | 153 | except KeyboardInterrupt: 154 | logger.info('Received interrupt, finishing running threads and not creating any new migrations') 155 | if pool is not None and pool_results is not None: 156 | wait_for_pool_end(logger, pool, pool_results) 157 | 158 | except vmodl.MethodFault as e: 159 | logger.critical('Caught vmodl fault: %s' % e.msg) 160 | return 1 161 | #except Exception as e: 162 | # logger.critical('Caught exception: %s' % str(e)) 163 | # return 1 164 | 165 | logger.info('Finished all tasks') 166 | return 0 167 | 168 | # Start program 169 | if __name__ == "__main__": 170 | main() 171 | -------------------------------------------------------------------------------- /multi-clone.py: -------------------------------------------------------------------------------- 1 | """ 2 | multi-clone is a Python script which allows you to clone a virtual machine or virtual machine template into multiple new virtual machines in a VMware vSphere environment. 3 | 4 | This script has the following capabilities: 5 | * Deploy a specified amount of virtual machines 6 | * Deploy in a specified datacenter 7 | * Deploy in a specified cluster 8 | * Deploy in a specified datastore 9 | * Deploy in a specified folder 10 | * Deploy in a specified resource pool 11 | * Specify if the cloned virtual machines need to be powered on 12 | * Print out information of the main network interface (mac and ip, either IPv4 or IPv6) 13 | * Run a post-processing script with 3 parameters (virtual machine name, mac and ip) 14 | * Instead of setting the basename, amount, resource pool and folder a CSV can be used 15 | * Print logging to a log file or stdout 16 | * Do this in a threaded way 17 | * Use linked clones to speed up cloning 18 | 19 | --- Using threads --- 20 | Deciding on the optimal amount of threads might need a bit of experimentation. Keep certain things in mind: 21 | * The optimal amount of threads depends on the IOPS of the datastore as each thread will start a template deployment task, which in turn starts copying the disks. 22 | * vCenter will, by default, only run 8 deployment tasks simultaniously while other tasks are queued, so setting the amount of threads to more than 8, is not really usefull. 23 | 24 | --- Using CSV file --- 25 | A CSV file can be provided with a line for each VM that needs to be created, with specific parameters for each VM. The format of each row should be (fields surrounded with <> are mandatory, fields surrounded with [] are optional): 26 | "";"[Datacenter]";"[Cluster]";"[Resouce Pool]";"[Folder]";"[Datastore]";"[MAC Address]";"[Post-processing Script]";"[Advanced VM Parameters in JSON format]" 27 | For instance: 28 | "Test01";"New-York";"Compute-Cluster-01";"Development";"IT";"VSAN-DS";"00:50:56:11:11:11";"run.sh";"{'parameter.1':'value.1','parameter.2':'value.2'}" 29 | 30 | --- Post-processing Script --- 31 | The Post-processing script is run for each VM created if it is provided either as a commandline parameter or as a field in the CSV. 32 | It is run with the following parameters: 33 | * virtual machine name, mac and ip : If Print IPs or Print MACs is enabled, combined with Power on 34 | * virtual machine name, mac: If a custom mac address was specified (even if VM is not powered on) 35 | * virtual machine name: If a power on is disabled and no custom mac address is enabled 36 | 37 | --- Usage --- 38 | Run 'multi-clone.py -h' for an overview 39 | 40 | --- Documentation --- 41 | https://github.com/pdellaert/vSphere-Python/blob/master/docs/multi-clone.md 42 | 43 | --- Author --- 44 | Philippe Dellaert 45 | 46 | --- License --- 47 | https://raw.github.com/pdellaert/vSphere-Python/master/LICENSE.md 48 | 49 | """ 50 | from __future__ import print_function 51 | 52 | from builtins import str 53 | from builtins import range 54 | import argparse 55 | import atexit 56 | import csv 57 | import getpass 58 | import json 59 | import logging 60 | import os.path 61 | import re 62 | import subprocess 63 | 64 | from time import sleep 65 | from pyVim.connect import SmartConnect, SmartConnectNoSSL, Disconnect 66 | from pyVmomi import vim, vmodl 67 | from multiprocessing.dummy import Pool as ThreadPool 68 | 69 | 70 | def get_args(): 71 | """ 72 | Supports the command-line arguments listed below. 73 | """ 74 | 75 | parser = argparse.ArgumentParser(description="Deploy a template into multiple VM's. You can get information returned with the name of the virtual machine created and it's main mac and ip address. Either in IPv4 or IPv6 format. You can specify which folder and/or resource pool the clone should be placed in. Verbose and debug output can either be send to stdout, or saved to a log file. A post-script can be specified for post-processing. And it can all be done in a number of parallel threads you specify. The script also provides the ability to use a CSV for a lot of it settings and if you want to specify the mac address of the clones (usefull for DHCP/PXE configuration).") 76 | parser.add_argument('-6', '--six', required=False, help='Get IPv6 address for VMs instead of IPv4', dest='ipv6', action='store_true') 77 | parser.add_argument('-b', '--basename', nargs=1, required=False, help='Basename of the newly deployed VMs', dest='basename', type=str) 78 | parser.add_argument('-c', '--count', nargs=1, required=False, help='Starting count, the name of the first VM deployed will be -, the second will be - (default = 1)', dest='count', type=int, default=[1]) 79 | parser.add_argument('-C', '--csv', nargs=1, required=False, help='An optional CSV overwritting the basename and count. For each line, a clone will be created. A line consits of the following fields, fields inside <> are mandatory, fields with [] are not: "";"[Datacenter]";"[Cluster]";"[Resouce Pool]";"[Folder]";"[Datastore]";"[MAC Address]";"[Post-processing Script]";"[Advanced VM Parameters in JSON format]"', dest='csvfile', type=str) 80 | parser.add_argument('--cluster', nargs=1, required=False, help='The cluster in which the new VMs should reside (default = same cluster as source virtual machine)', dest='cluster', type=str) 81 | parser.add_argument('-d', '--debug', required=False, help='Enable debug output', dest='debug', action='store_true') 82 | parser.add_argument('--datacenter', nargs=1, required=False, help='The datacenter in which the new VMs should reside (default = same datacenter as source virtual machine)', dest='datacenter', type=str) 83 | parser.add_argument('--datastore', nargs=1, required=False, help='The datastore in which the new VMs should reside (default = same datastore as source virtual machine)', dest='datastore', type=str) 84 | parser.add_argument('--folder', nargs=1, required=False, help='The folder in which the new VMs should reside (default = same folder as source virtual machine)', dest='folder', type=str) 85 | parser.add_argument('-H', '--host', nargs=1, required=True, help='The vCenter or ESXi host to connect to', dest='host', type=str) 86 | parser.add_argument('-i', '--print-ips', required=False, help='Enable IP output', dest='ips', action='store_true') 87 | parser.add_argument('-m', '--print-macs', required=False, help='Enable MAC output', dest='macs', action='store_true') 88 | parser.add_argument('-l', '--log-file', nargs=1, required=False, help='File to log to (default = stdout)', dest='logfile', type=str) 89 | parser.add_argument('-L', '--linked', required=False, help='Enable linked cloning', dest='linked', action='store_true') 90 | parser.add_argument('--snapshot', required=False, help='Snapshot to be used for linked cloning', dest='snapshot', type=str) 91 | parser.add_argument('-n', '--number', nargs=1, required=False, help='Amount of VMs to deploy (default = 1)', dest='amount', type=int, default=[1]) 92 | parser.add_argument('-o', '--port', nargs=1, required=False, help='Server port to connect to (default = 443)', dest='port', type=int, default=[443]) 93 | parser.add_argument('-p', '--password', nargs=1, required=False, help='The password with which to connect to the host. If not specified, the user is prompted at runtime for a password', dest='password', type=str) 94 | parser.add_argument('-P', '--disable-power-on', required=False, help='Disable power on of cloned VMs', dest='nopoweron', action='store_true') 95 | parser.add_argument('--resource-pool', nargs=1, required=False, help='The resource pool in which the new VMs should reside, (default = Resources, the root resource pool)', dest='resource_pool', type=str) 96 | parser.add_argument('-s', '--post-script', nargs=1, required=False, help='Script to be called after each VM is created and booted. Arguments passed: name mac-address ip-address', dest='post_script', type=str) 97 | parser.add_argument('-S', '--disable-SSL-certificate-verification', required=False, help='Disable SSL certificate verification on connect', dest='nosslcheck', action='store_true') 98 | parser.add_argument('-t', '--template', nargs=1, required=True, help='Template to deploy', dest='template', type=str) 99 | parser.add_argument('-T', '--threads', nargs=1, required=False, help='Amount of threads to use. Choose the amount of threads with the speed of your datastore in mind, each thread starts the creation of a virtual machine. (default = 1)', dest='threads', type=int, default=[1]) 100 | parser.add_argument('-u', '--user', nargs=1, required=True, help='The username with which to connect to the host', dest='username', type=str) 101 | parser.add_argument('-v', '--verbose', required=False, help='Enable verbose output', dest='verbose', action='store_true') 102 | parser.add_argument('-w', '--wait-max', nargs=1, required=False, help='Maximum amount of seconds to wait when gathering information (default = 120)', dest='maxwait', type=int, default=[120]) 103 | args = parser.parse_args() 104 | return args 105 | 106 | 107 | def find_obj(si, logger, name, vimtype, threaded=False): 108 | """ 109 | Find an object in vSphere by it's name and return it 110 | """ 111 | 112 | content = si.content 113 | obj_view = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True) 114 | obj_list = obj_view.view 115 | 116 | for obj in obj_list: 117 | if threaded: 118 | logger.debug('THREAD %s - Checking Object "%s"' % (name, obj.name)) 119 | else: 120 | logger.debug('Checking object "%s"' % obj.name) 121 | if obj.name == name: 122 | if threaded: 123 | logger.debug('THREAD %s - Found object %s' % (name, obj.name)) 124 | else: 125 | logger.debug('Found object %s' % obj.name) 126 | return obj 127 | return None 128 | 129 | 130 | def find_mac_ip(logger, vm, maxwait, ipv6=False, threaded=False): 131 | """ 132 | Find the external mac and IP of a virtual machine and return it 133 | """ 134 | 135 | mac = None 136 | ip = None 137 | waitcount = 0 138 | 139 | while waitcount < maxwait: 140 | if threaded: 141 | logger.debug('THREAD %s - Waited for %s seconds, gathering net information' % (vm.config.name, waitcount)) 142 | else: 143 | logger.debug('Waited for %s seconds, gathering net information for virtual machine %s' % (waitcount, vm.config.name)) 144 | net_info = vm.guest.net 145 | 146 | for cur_net in net_info: 147 | if cur_net.macAddress: 148 | if threaded: 149 | logger.debug('THREAD %s - Mac address %s found' % (vm.config.name, cur_net.macAddress)) 150 | else: 151 | logger.debug('Mac address %s found for virtual machine %s' % (cur_net.macAddress, vm.config.name)) 152 | mac = cur_net.macAddress 153 | if mac and cur_net.ipConfig: 154 | if cur_net.ipConfig.ipAddress: 155 | for cur_ip in cur_net.ipConfig.ipAddress: 156 | if threaded: 157 | logger.debug('THREAD %s - Checking ip address %s' % (vm.config.name, cur_ip.ipAddress)) 158 | else: 159 | logger.debug('Checking ip address %s for virtual machine %s' % (cur_ip.ipAddress, vm.config.name)) 160 | if ipv6 and re.match('\d{1,4}\:.*', cur_ip.ipAddress) and not re.match('fe83\:.*', cur_ip.ipAddress): 161 | ip = cur_ip.ipAddress 162 | elif not ipv6 and re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', cur_ip.ipAddress) and cur_ip.ipAddress != '127.0.0.1': 163 | ip = cur_ip.ipAddress 164 | if ip: 165 | if threaded: 166 | logger.info('THREAD %s - Mac %s and ip %s found' % (vm.config.name, mac, ip)) 167 | else: 168 | logger.info('Mac %s and ip %s found for virtual machine %s' % (mac, ip, vm.config.name)) 169 | return [mac, ip] 170 | 171 | if threaded: 172 | logger.debug('THREAD %s - No IP found, waiting 5 seconds and retrying' % vm.config.name) 173 | else: 174 | logger.debug('No IP found for virtual machine %s, waiting 5 seconds and retrying' % vm.config.name) 175 | waitcount += 5 176 | sleep(5) 177 | if mac: 178 | if threaded: 179 | logger.info('THREAD %s - Found mac address %s, No ip address found' % (vm.config.name, mac)) 180 | else: 181 | logger.info('Found mac address %s, No ip address found for virtual machine %s' % (mac, vm.config.name)) 182 | return [mac, ''] 183 | if threaded: 184 | logger.info('THREAD %s - Unable to find mac address or ip address' % vm.config.name) 185 | else: 186 | logger.info('Unable to find mac address or ip address for virtual machine %s' % vm.config.name) 187 | return None 188 | 189 | 190 | def run_post_script(logger, post_script, vm, mac_ip, custom_mac): 191 | """ 192 | Runs a post script for a vm 193 | """ 194 | if mac_ip: 195 | logger.info('Running post-script command: %s %s %s %s' % (post_script, vm.config.name, mac_ip[0], mac_ip[1])) 196 | retcode = subprocess.call([post_script, vm.config.name, mac_ip[0], mac_ip[1]]) 197 | logger.debug('Received return code %s for command: %s %s %s %s' % (retcode, post_script, vm.config.name, mac_ip[0], mac_ip[1])) 198 | elif custom_mac: 199 | logger.info('Running post-script command: %s %s %s' % (post_script, vm.config.name, custom_mac)) 200 | retcode = subprocess.call([post_script, vm.config.name]) 201 | logger.debug('Received return code %s for command: %s %s' % (retcode, post_script, vm.config.name)) 202 | else: 203 | logger.info('Running post-script command: %s %s' % (post_script, vm.config.name)) 204 | retcode = subprocess.call([post_script, vm.config.name]) 205 | logger.debug('Received return code %s for command: %s %s' % (retcode, post_script, vm.config.name)) 206 | return retcode 207 | 208 | 209 | def get_snapshots_by_name_recursively(snapshots, snapname): 210 | snap_obj = [] 211 | for snapshot in snapshots: 212 | if snapshot.name == snapname: 213 | snap_obj.append(snapshot) 214 | else: 215 | snap_obj = snap_obj + get_snapshots_by_name_recursively(snapshot.childSnapshotList, snapname) 216 | return snap_obj 217 | 218 | 219 | def vm_clone_handler_wrapper(args): 220 | """ 221 | Wrapping arround vm_clone_handler 222 | """ 223 | 224 | return vm_clone_handler(*args) 225 | 226 | 227 | def vm_clone_handler(si, logger, linked, vm_name, datacenter_name, cluster_name, resource_pool_name, folder_name, datastore_name, custom_mac, ipv6, maxwait, post_script, power_on, print_ips, print_macs, template, template_vm, template_snapshot, mac_ip_pool, mac_ip_pool_results, adv_parameters): 228 | """ 229 | Will handle the thread handling to clone a virtual machine and run post processing 230 | """ 231 | 232 | run_loop = True 233 | vm = None 234 | 235 | logger.debug('THREAD %s - started' % vm_name) 236 | logger.info('THREAD %s - Trying to clone %s to new virtual machine' % (vm_name, template)) 237 | 238 | # Find the correct Datacenter 239 | datacenter = None 240 | if datacenter_name: 241 | logger.debug('THREAD %s - Finding datacenter %s' % (vm_name, datacenter_name)) 242 | datacenter = find_obj(si, logger, datacenter_name, [vim.Datacenter], False) 243 | if datacenter is None: 244 | logger.critical('THREAD %s - Unable to find datacenter %s' % (vm_name, datacenter_name)) 245 | return 1 246 | logger.info('THREAD %s - Datacenter %s found' % (vm_name, datacenter_name)) 247 | 248 | # Find the correct Cluster 249 | cluster = None 250 | if cluster_name: 251 | logger.debug('THREAD %s - Finding cluster %s' % (vm_name, cluster_name)) 252 | cluster = find_obj(si, logger, cluster_name, [vim.ClusterComputeResource], False) 253 | if cluster is None: 254 | logger.critical('THREAD %s - Unable to find cluster %s' % (vm_name, cluster_name)) 255 | return 1 256 | logger.info('THREAD %s - Cluster %s found' % (vm_name, cluster_name)) 257 | 258 | # Find the correct Resource Pool 259 | resource_pool = None 260 | if resource_pool_name: 261 | logger.debug('THREAD %s - Finding resource pool %s' % (vm_name, resource_pool_name)) 262 | resource_pool = find_obj(si, logger, resource_pool_name, [vim.ResourcePool], False) 263 | if resource_pool is None: 264 | logger.critical('THREAD %s - Unable to find resource pool %s' % (vm_name, resource_pool_name)) 265 | return 1 266 | logger.info('THREAD %s - Resource pool %s found' % (vm_name, resource_pool_name)) 267 | elif cluster: 268 | logger.info('THREAD %s - No resource pool specified, but a cluster is. Using its root resource pool.' % vm_name) 269 | resource_pool = cluster.resourcePool 270 | else: 271 | logger.info('THREAD %s - No resource pool specified. Using the default resource pool.' % vm_name) 272 | resource_pool = find_obj(si, logger, 'Resources', [vim.ResourcePool], False) 273 | 274 | # Find the correct folder 275 | folder = None 276 | if folder_name: 277 | logger.debug('THREAD %s - Finding folder %s' % (vm_name, folder_name)) 278 | folder = find_obj(si, logger, folder_name, [vim.Folder], False) 279 | if folder is None: 280 | logger.critical('THREAD %s - Unable to find folder %s' % (vm_name, folder_name)) 281 | return 1 282 | logger.info('THREAD %s - Folder %s found' % (vm_name, folder_name)) 283 | elif datacenter: 284 | logger.info('THREAD %s - Setting folder to datacenter root folder as a datacenter has been defined' % vm_name) 285 | folder = datacenter.vmFolder 286 | else: 287 | logger.info('THREAD %s - Setting folder to template folder as default' % vm_name) 288 | folder = template_vm.parent 289 | 290 | # Find the correct datastore 291 | datastore = None 292 | if datastore_name: 293 | logger.debug('THREAD %s - Finding datastore %s' % (vm_name, datastore_name)) 294 | datastore = find_obj(si, logger, datastore_name, [vim.Datastore], False) 295 | if datastore is None: 296 | logger.critical('THREAD %s - Unable to find datastore %s' % (vm_name, datastore_name)) 297 | return 1 298 | logger.info('THREAD %s - Datastore %s found' % (vm_name, datastore_name)) 299 | else: 300 | datastore = find_obj(si, logger, template_vm.datastore[0].info.name, [vim.Datastore], False) 301 | 302 | # Creating necessary specs 303 | logger.debug('THREAD %s - Creating relocate spec' % vm_name) 304 | relocate_spec = vim.vm.RelocateSpec() 305 | if resource_pool: 306 | logger.debug('THREAD %s - Resource pool found, using' % vm_name) 307 | relocate_spec.pool = resource_pool 308 | if datastore: 309 | logger.debug('THREAD %s - Datastore found, using' % vm_name) 310 | relocate_spec.datastore = datastore 311 | if linked: 312 | logger.debug('THREAD %s - Linked clone enabled' % vm_name) 313 | relocate_spec.diskMoveType = vim.vm.RelocateSpec.DiskMoveOptions.createNewChildDiskBacking 314 | 315 | logger.debug('THREAD %s - Creating clone spec' % vm_name) 316 | clone_spec = vim.vm.CloneSpec(powerOn=False, template=False, location=relocate_spec) 317 | if linked: 318 | clone_spec.snapshot = template_snapshot[0].snapshot 319 | 320 | if find_obj(si, logger, vm_name, [vim.VirtualMachine], True): 321 | logger.warning('THREAD %s - Virtual machine already exists, not creating' % vm_name) 322 | run_loop = False 323 | else: 324 | logger.debug('THREAD %s - Creating clone task' % vm_name) 325 | task = template_vm.Clone(name=vm_name, folder=folder, spec=clone_spec) 326 | logger.info('THREAD %s - Cloning task created' % vm_name) 327 | logger.info('THREAD %s - Checking task for completion. This might take a while' % vm_name) 328 | 329 | while run_loop: 330 | info = task.info 331 | logger.debug('THREAD %s - Checking clone task' % vm_name) 332 | if info.state == vim.TaskInfo.State.success: 333 | logger.info('THREAD %s - Cloned and running' % vm_name) 334 | vm = info.result 335 | run_loop = False 336 | break 337 | elif info.state == vim.TaskInfo.State.running: 338 | logger.debug('THREAD %s - Cloning task is at %s percent' % (vm_name, info.progress)) 339 | elif info.state == vim.TaskInfo.State.queued: 340 | logger.debug('THREAD %s - Cloning task is queued' % vm_name) 341 | elif info.state == vim.TaskInfo.State.error: 342 | if info.error.fault: 343 | logger.info('THREAD %s - Cloning task has quit with error: %s' % (vm_name, info.error.fault.faultMessage)) 344 | else: 345 | logger.info('THREAD %s - Cloning task has quit with cancelation' % vm_name) 346 | run_loop = False 347 | break 348 | logger.debug('THREAD %s - Sleeping 2 seconds for new check' % vm_name) 349 | sleep(2) 350 | 351 | if vm and custom_mac is not None and custom_mac is not '': 352 | vm_ethernet = None 353 | config_spec = None 354 | logger.info('THREAD %s - Trying to set mac to %s' % (vm_name, custom_mac)) 355 | logger.debug('THREAD %s - Searching for ethernet device' % vm_name) 356 | for vm_device in vm.config.hardware.device: 357 | if isinstance(vm_device, vim.vm.device.VirtualEthernetCard): 358 | logger.debug('THREAD %s - Found ethernet device' % vm_name) 359 | vm_ethernet = vm_device 360 | break 361 | 362 | if vm_ethernet is not None: 363 | vm_ethernet.addressType = "Manual" 364 | vm_ethernet.macAddress = custom_mac 365 | logger.debug('THREAD %s - Creating of device spec for ethernet card' % vm_name) 366 | vm_device_spec = vim.vm.device.VirtualDeviceSpec(device=vm_ethernet, operation=vim.vm.device.VirtualDeviceSpec.Operation.edit) 367 | logger.debug('THREAD %s - Creating of config spec for VM' % vm_name) 368 | config_spec = vim.vm.ConfigSpec(deviceChange=[vm_device_spec]) 369 | logger.info('THREAD %s - Applying MAC address change. This might take a couple of seconds' % vm_name) 370 | config_task = vm.ReconfigVM_Task(spec=config_spec) 371 | logger.debug('THREAD %s - Waiting fo MAC address change to complete' % vm_name) 372 | run_loop = True 373 | while run_loop: 374 | info = task.info 375 | if info.state == vim.TaskInfo.State.success: 376 | logger.debug('THREAD %s - Mac address change completed' % vm_name) 377 | run_loop = False 378 | break 379 | elif info.state == vim.TaskInfo.State.error: 380 | if info.error.fault: 381 | logger.info('THREAD %s - MAC address change has quit with error: %s' % (vm_name, info.error.fault.faultMessage)) 382 | else: 383 | logger.info('THREAD %s - MAC address change has quit with cancelation' % vm_name) 384 | run_loop = False 385 | break 386 | sleep(2) 387 | 388 | if vm and adv_parameters is not None and adv_parameters is not '': 389 | logger.info('THREAD %s - Setting advanced parameters' % vm_name) 390 | logger.debug('THREAD %s - Loading JSON data: %s' % (vm_name, adv_parameters)) 391 | adv_parameters_dict = json.loads(adv_parameters) 392 | vm_option_values = [] 393 | for key, value in adv_parameters_dict.items(): 394 | logger.debug('THREAD %s - Creating option value for key %s and value %s' % (vm_name, key, value)) 395 | vm_option_values.append(vim.option.OptionValue(key=key, value=value)) 396 | logger.debug('THREAD %s - Creating of config spec for VM' % vm_name) 397 | config_spec = vim.vm.ConfigSpec(extraConfig=vm_option_values) 398 | logger.info('THREAD %s - Applying advanced parameters. This might take a couple of seconds' % vm_name) 399 | config_task = vm.ReconfigVM_Task(spec=config_spec) 400 | logger.debug('THREAD %s - Waiting for the advanced paramerter to be applied' % vm_name) 401 | run_loop = True 402 | while run_loop: 403 | info = config_task.info 404 | if info.state == vim.TaskInfo.State.success: 405 | logger.debug('THREAD %s - Advanced parameters applied' % vm_name) 406 | run_loop = False 407 | break 408 | elif info.state == vim.TaskInfo.State.error: 409 | if info.error.fault: 410 | logger.info('THREAD %s - Applying advanced parameters has quit with error: %s' % (vm_name, info.error.fault.faultMessage)) 411 | else: 412 | logger.info('THREAD %s - Applying advanced parameters has quit with cancelation' % vm_name) 413 | run_loop = False 414 | break 415 | sleep(2) 416 | 417 | if vm and power_on: 418 | logger.info('THREAD %s - Powering on VM. This might take a couple of seconds' % vm_name) 419 | power_on_task = vm.PowerOn() 420 | logger.debug('THREAD %s - Waiting fo VM to power on' % vm_name) 421 | run_loop = True 422 | while run_loop: 423 | info = power_on_task.info 424 | if info.state == vim.TaskInfo.State.success: 425 | run_loop = False 426 | break 427 | elif info.state == vim.TaskInfo.State.error: 428 | if info.error.fault: 429 | logger.info('THREAD %s - Power on has quit with error: %s' % (vm_name, info.error.fault.faultMessage)) 430 | else: 431 | logger.info('THREAD %s - Power on has quit with cancelation' % vm_name) 432 | run_loop = False 433 | break 434 | sleep(2) 435 | 436 | if vm and power_on and (post_script or print_ips or print_macs): 437 | logger.debug('THREAD %s - Creating mac, ip and post-script processing thread' % vm_name) 438 | mac_ip_pool_results.append(mac_ip_pool.apply_async(vm_mac_ip_handler, (logger, vm, ipv6, maxwait, post_script, power_on, print_ips, print_macs, custom_mac))) 439 | elif vm and (post_script or print_ips or print_macs): 440 | logger.error('THREAD %s - Power on is disabled, printing of IP and Mac is not possible' % vm_name) 441 | 442 | return vm 443 | 444 | 445 | def vm_mac_ip_handler(logger, vm, ipv6, maxwait, post_script, power_on, print_ips, print_macs, custom_mac): 446 | """ 447 | Gather mac, ip and run post-script for a cloned virtual machine 448 | """ 449 | 450 | mac_ip = None 451 | if print_macs or print_ips: 452 | logger.info('THREAD %s - Gathering mac and ip' % vm.config.name) 453 | mac_ip = find_mac_ip(logger, vm, maxwait, ipv6, True) 454 | if mac_ip and print_macs and print_ips: 455 | logger.info('THREAD %s - Printing mac and ip information: %s %s %s' % (vm.config.name, vm.config.name, mac_ip[0], mac_ip[1])) 456 | print('%s %s %s' % (vm.config.name, mac_ip[0], mac_ip[1])) 457 | elif mac_ip and print_macs: 458 | logger.info('THREAD %s - Printing mac information: %s %s' % (vm.config.name, vm.config.name, mac_ip[0])) 459 | print('%s %s' % (vm.config.name, mac_ip[0])) 460 | elif mac_ip and print_ips: 461 | logger.info('THREAD %s - Printing ip information: %s %s' % (vm.config.name, vm.config.name, mac_ip[1])) 462 | print('%s %s' % (vm.config.name, mac_ip[1])) 463 | elif print_macs or print_ips: 464 | logger.error('THREAD %s - Unable to find mac or ip information within %s seconds' % (vm.config.name, maxwait)) 465 | 466 | if post_script: 467 | retcode = run_post_script(logger, post_script, vm, mac_ip, custom_mac) 468 | if retcode > 0: 469 | logger.warning('THREAD %s - Post processing failed.' % vm.config.name) 470 | 471 | 472 | def main(): 473 | """ 474 | Clone a VM or template into multiple VMs with logical names with numbers and allow for post-processing 475 | """ 476 | 477 | # Handling arguments 478 | args = get_args() 479 | ipv6 = args.ipv6 480 | amount = args.amount[0] 481 | basename = None 482 | if args.basename: 483 | basename = args.basename[0] 484 | count = args.count[0] 485 | csvfile = None 486 | if args.csvfile: 487 | csvfile = args.csvfile[0] 488 | debug = args.debug 489 | cluster_name = None 490 | if args.cluster: 491 | cluster_name = args.cluster[0] 492 | datacenter_name = None 493 | if args.datacenter: 494 | datacenter_name = args.datacenter[0] 495 | datastore_name = None 496 | if args.datastore: 497 | datastore_name = args.datastore[0] 498 | folder_name = None 499 | if args.folder: 500 | folder_name = args.folder[0] 501 | host = args.host[0] 502 | print_ips = args.ips 503 | print_macs = args.macs 504 | log_file = None 505 | if args.logfile: 506 | log_file = args.logfile[0] 507 | port = args.port[0] 508 | post_script = None 509 | if args.post_script: 510 | post_script = args.post_script[0] 511 | password = None 512 | if args.password: 513 | password = args.password[0] 514 | power_on = not args.nopoweron 515 | resource_pool_name = None 516 | if args.resource_pool: 517 | resource_pool_name = args.resource_pool[0] 518 | nosslcheck = args.nosslcheck 519 | template = args.template[0] 520 | threads = args.threads[0] 521 | username = args.username[0] 522 | verbose = args.verbose 523 | maxwait = args.maxwait[0] 524 | linked = args.linked 525 | snapshot = None 526 | if args.snapshot: 527 | snapshot = args.snapshot 528 | 529 | # Logging settings 530 | if debug: 531 | log_level = logging.DEBUG 532 | elif verbose: 533 | log_level = logging.INFO 534 | else: 535 | log_level = logging.WARNING 536 | 537 | if log_file: 538 | logging.basicConfig(filename=log_file, format='%(asctime)s %(levelname)s %(message)s', level=log_level) 539 | else: 540 | logging.basicConfig(filename=log_file, format='%(asctime)s %(levelname)s %(message)s', level=log_level) 541 | logger = logging.getLogger(__name__) 542 | 543 | # Getting user password 544 | if password is None: 545 | logger.debug('No command line password received, requesting password from user') 546 | password = getpass.getpass(prompt='Enter password for vCenter %s for user %s: ' % (host, username)) 547 | 548 | try: 549 | si = None 550 | try: 551 | logger.info('Connecting to server %s:%s with username %s' % (host, port, username)) 552 | if nosslcheck: 553 | si = SmartConnectNoSSL(host=host, user=username, pwd=password, port=int(port)) 554 | else: 555 | si = SmartConnect(host=host, user=username, pwd=password, port=int(port)) 556 | except IOError as e: 557 | pass 558 | 559 | if not si: 560 | logger.error('Could not connect to host %s with user %s and specified password' % (host, username)) 561 | return 1 562 | 563 | logger.debug('Registering disconnect at exit') 564 | atexit.register(Disconnect, si) 565 | 566 | # Find the correct VM 567 | logger.debug('Finding template %s' % template) 568 | template_vm = find_obj(si, logger, template, [vim.VirtualMachine], False) 569 | if template_vm is None: 570 | logger.error('Unable to find template %s' % template) 571 | return 1 572 | logger.info('Template %s found' % template) 573 | 574 | # Finding the snapshot if linked 575 | template_snapshot = None 576 | if linked and not snapshot: 577 | logger.error('When linked cloning is enabled, a snapshot has to be provided.') 578 | return 1 579 | elif linked: 580 | template_snapshot = get_snapshots_by_name_recursively(snapshots=template_vm.snapshot.rootSnapshotList, snapname=snapshot) 581 | if len(template_snapshot) != 1: 582 | logger.error('Snapshot %s not found.' % snapshot) 583 | return 1 584 | logger.info('Snapshot %s found.' % snapshot) 585 | 586 | # Pool handling 587 | logger.debug('Setting up pools and threads') 588 | pool = ThreadPool(threads) 589 | mac_ip_pool = ThreadPool(threads) 590 | mac_ip_pool_results = [] 591 | vm_specs = [] 592 | logger.debug('Pools created with %s threads' % threads) 593 | 594 | if csvfile is None: 595 | # Generate VM names 596 | logger.debug('No CSV found working with amount and basename') 597 | logger.debug('Creating thread specifications') 598 | vm_names = [] 599 | for a in range(1, amount + 1): 600 | vm_names.append('%s-%i' % (basename, count)) 601 | count += 1 602 | 603 | vm_names.sort() 604 | for vm_name in vm_names: 605 | vm_specs.append((si, logger, linked, vm_name, datacenter_name, cluster_name, resource_pool_name, folder_name, datastore_name, None, ipv6, maxwait, post_script, power_on, print_ips, print_macs, template, template_vm, template_snapshot, mac_ip_pool, mac_ip_pool_results, None)) 606 | else: 607 | # CSV fields: 608 | # VM Name, Resource Pool, Folder, MAC Address, Post Script 609 | logger.debug('Parsing csv %s' % csvfile) 610 | 611 | if not os.path.isfile(csvfile): 612 | logger.critical('CSV file %s does not exist, exiting' % csvfile) 613 | return 1 614 | 615 | with open(csvfile, 'rb') as tasklist: 616 | taskreader = csv.reader(tasklist, delimiter=';', quotechar='"') 617 | for row in taskreader: 618 | logger.debug('Found CSV row: %s' % ','.join(row)) 619 | # VM Name 620 | if row[0] is None or row[0] is '': 621 | logger.warning('No VM name specified, skipping this vm creation') 622 | continue 623 | else: 624 | cur_vm_name = row[0] 625 | # Datacenter 626 | if row[1] is None or row[1] is '': 627 | cur_datacenter_name = datacenter_name 628 | else: 629 | cur_datacenter_name = row[1] 630 | # Cluster 631 | if row[2] is None or row[2] is '': 632 | cur_cluster_name = cluster_name 633 | else: 634 | cur_cluster_name = row[2] 635 | # Resource Pool 636 | if row[3] is None or row[3] is '': 637 | cur_resource_pool_name = resource_pool_name 638 | else: 639 | cur_resource_pool_name = row[3] 640 | # Folder 641 | if row[4] is None or row[4] is '': 642 | cur_folder_name = folder_name 643 | else: 644 | cur_folder_name = row[4] 645 | # Datastore 646 | if row[5] is None or row[5] is '': 647 | cur_datastore_name = datastore_name 648 | else: 649 | cur_datastore_name = row[5] 650 | # MAC 651 | if row[6] is None or row[6] is '': 652 | custom_mac = None 653 | else: 654 | custom_mac = row[6] 655 | # Post script 656 | if row[7] is None or row[7] is '': 657 | cur_post_script = post_script 658 | else: 659 | cur_post_script = row[7] 660 | # Advanced parameters 661 | if row[8] is None or row[8] is '': 662 | cur_adv_parameters = None 663 | else: 664 | cur_adv_parameters = row[8] 665 | 666 | # Creating VM 667 | vm_specs.append((si, logger, linked, cur_vm_name, cur_datacenter_name, cur_cluster_name, cur_resource_pool_name, cur_folder_name, cur_datastore_name, custom_mac, ipv6, maxwait, cur_post_script, power_on, print_ips, print_macs, template, template_vm, template_snapshot, mac_ip_pool, mac_ip_pool_results, cur_adv_parameters)) 668 | 669 | logger.debug('Running virtual machine clone pool') 670 | pool.map(vm_clone_handler_wrapper, vm_specs) 671 | 672 | logger.debug('Closing virtual machine clone pool') 673 | pool.close() 674 | pool.join() 675 | 676 | logger.debug('Waiting for all mac, ip and post-script processes') 677 | for running_task in mac_ip_pool_results: 678 | running_task.wait() 679 | 680 | logger.debug('Closing mac, ip and post-script processes') 681 | mac_ip_pool.close() 682 | mac_ip_pool.join() 683 | 684 | except vmodl.MethodFault as e: 685 | logger.critical('Caught vmodl fault: %s' % e.msg) 686 | return 1 687 | except Exception as e: 688 | logger.critical('Caught exception: %s' % str(e)) 689 | return 1 690 | 691 | logger.info('Finished all tasks') 692 | return 0 693 | 694 | # Start program 695 | if __name__ == "__main__": 696 | main() 697 | -------------------------------------------------------------------------------- /pysphere-get-vm-ips.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, re, getpass, argparse, subprocess 3 | from time import sleep 4 | from pysphere import MORTypes, VIServer, VITask, VIProperty, VIMor, VIException 5 | from pysphere.vi_virtual_machine import VIVirtualMachine 6 | 7 | def print_verbose(message): 8 | if verbose: 9 | print message 10 | 11 | def find_vm(name): 12 | try: 13 | vm = con.get_vm_by_name(name) 14 | return vm 15 | except VIException: 16 | return None 17 | 18 | def find_ip(vm,ipv6=False): 19 | ips = None 20 | net_info = vm.get_property('net',False) 21 | if net_info: 22 | for ip in net_info[0]['ip_addresses']: 23 | if ipv6 and re.match('\d{1,4}\:.*',ip) and not re.match('fe83\:.*',ip): 24 | print_verbose('IPv6 address found: %s' % ip) 25 | ips = str(ips) + ';' + ip 26 | #return ip 27 | elif not ipv6 and re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',ip) and ip != '127.0.0.1': 28 | print_verbose('IPv4 address found: %s' % ip) 29 | ips = str(ips) + ';' + ip 30 | #return ip 31 | return ips 32 | 33 | parser = argparse.ArgumentParser(description="Deploy a template into multiple VM's") 34 | parser.add_argument('-6', '--six', required=False, help='Get IPv6 address for VMs instead of IPv4', dest='ipv6', action='store_true') 35 | parser.add_argument('-a', '--all', required=False, help='Get address for all powered on VMs', dest='allvms', action='store_true') 36 | parser.add_argument('-n', '--name', nargs=1, required=False, help='VM Name', dest='vmname', type=str) 37 | parser.add_argument('-s', '--server', nargs=1, required=True, help='The vCenter or ESXi server to connect to', dest='server', type=str) 38 | parser.add_argument('-u', '--user', nargs=1, required=True, help='The username with which to connect to the server', dest='username', type=str) 39 | parser.add_argument('-v', '--verbose', required=False, help='Enable verbose output', dest='verbose', action='store_true') 40 | 41 | args = parser.parse_args() 42 | 43 | ipv6 = args.ipv6 44 | allvms = args.allvms 45 | vmname = None 46 | if args.vmname: 47 | vmname = args.vmname[0] 48 | server = args.server[0] 49 | username = args.username[0] 50 | verbose = args.verbose 51 | 52 | # Asking Users password for server 53 | password=getpass.getpass(prompt='Enter password for vCenter %s for user %s: ' % (server,username)) 54 | 55 | # Connecting to server 56 | print_verbose('Connecting to server %s with username %s' % (server,username)) 57 | con = VIServer() 58 | con.connect(server,username,password) 59 | print_verbose('Connected to server %s' % server) 60 | print_verbose('Server type: %s' % con.get_server_type()) 61 | print_verbose('API version: %s' % con.get_api_version()) 62 | 63 | if allvms: 64 | vms = con.get_registered_vms(None, None, None, 'poweredOn', None) 65 | for vmpath in vms: 66 | print_verbose('================================================================================') 67 | vm = con.get_vm_by_path(vmpath) 68 | vmname = vm.get_property('name') 69 | ip = find_ip(vm,ipv6) 70 | if ip: 71 | print '%s : %s' % (vmname,ip) 72 | else: 73 | print 'ERROR: IP for VM %s not found' % vmname 74 | else: 75 | print_verbose('================================================================================') 76 | print_verbose('Trying to find IP for VM %s' % vmname) 77 | vm = find_vm(vmname) 78 | if vm: 79 | ip = find_ip(vm,ipv6) 80 | if ip: 81 | print '%s : %s' % (vmname,ip) 82 | else: 83 | print 'ERROR: IP for VM %s not found' % vmname 84 | else: 85 | print 'ERROR: %s not found' % vmname 86 | 87 | # Disconnecting from server 88 | con.disconnect() 89 | -------------------------------------------------------------------------------- /pysphere-multi-clone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, re, getpass, argparse, subprocess 3 | from time import sleep 4 | from pysphere import MORTypes, VIServer, VITask, VIProperty, VIMor, VIException 5 | from pysphere.vi_virtual_machine import VIVirtualMachine 6 | 7 | def print_verbose(message): 8 | if verbose: 9 | print message 10 | 11 | def find_vm(name): 12 | try: 13 | vm = con.get_vm_by_name(name) 14 | return vm 15 | except VIException: 16 | return None 17 | 18 | def find_resource_pool(name): 19 | rps = con.get_resource_pools() 20 | for mor, path in rps.iteritems(): 21 | print_verbose('Parsing RP %s' % path) 22 | if re.match('.*%s' % name,path): 23 | return mor 24 | return None 25 | 26 | def find_folder(name): 27 | folders = con._get_managed_objects(MORTypes.Folder) 28 | try: 29 | for mor, folder_name in folders.iteritems(): 30 | print_verbose('Parsing folder %s' % folder_name) 31 | if folder_name == name: 32 | return mor 33 | except IndexError: 34 | return None 35 | return None 36 | 37 | def run_post_script(name,ip): 38 | print_verbose('Running post script: %s %s %s' % (post_script,name,ip)) 39 | retcode = subprocess.call([post_script,name,ip]) 40 | if retcode < 0: 41 | print 'ERROR: %s %s %s : Returned a non-zero result' % (post_script,name,ip) 42 | sys.exit(1) 43 | 44 | def find_ip(vm,ipv6=False): 45 | net_info = vm.get_property('net',False) 46 | waitcount = 0 47 | while net_info is None: 48 | if waitcount > maxwait: 49 | break 50 | net_info = vm.get_property('net',False) 51 | print_verbose('Waiting 5 seconds ...') 52 | waitcount += 5 53 | sleep(5) 54 | if net_info: 55 | for ip in net_info[0]['ip_addresses']: 56 | if ipv6 and re.match('\d{1,4}\:.*',ip) and not re.match('fe83\:.*',ip): 57 | print_verbose('IPv6 address found: %s' % ip) 58 | return ip 59 | elif not ipv6 and re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',ip) and ip != '127.0.0.1': 60 | print_verbose('IPv4 address found: %s' % ip) 61 | return ip 62 | print_verbose('Timeout expired: No IP address found') 63 | return None 64 | 65 | parser = argparse.ArgumentParser(description="Deploy a template into multiple VM's") 66 | parser.add_argument('-6', '--six', required=False, help='Get IPv6 address for VMs instead of IPv4', dest='ipv6', action='store_true') 67 | parser.add_argument('-b', '--basename', nargs=1, required=True, help='Basename of the newly deployed VMs', dest='basename', type=str) 68 | parser.add_argument('-c', '--count', nargs=1, required=False, help='Starting count, the name of the first VM deployed will be -, the second will be - (default=1)', dest='count', type=int, default=[1]) 69 | parser.add_argument('-f', '--folder', nargs=1, required=False, help='The folder in which the new VMs should reside', dest='folder', type=str) 70 | parser.add_argument('-n', '--number', nargs=1, required=False, help='Amount of VMs to deploy (default=1)', dest='amount', type=int, default=[1]) 71 | parser.add_argument('-p', '--post-script', nargs=1, required=False, help='Script to be called after each VM is created and booted. Arguments passed: name ip-address', dest='post_script', type=str) 72 | parser.add_argument('-r', '--resource-pool', nargs=1, required=False, help='The resource pool in which the new VMs should reside', dest='resource_pool', type=str) 73 | parser.add_argument('-s', '--server', nargs=1, required=True, help='The vCenter or ESXi server to connect to', dest='server', type=str) 74 | parser.add_argument('-t', '--template', nargs=1, required=True, help='Template to deploy', dest='template', type=str) 75 | parser.add_argument('-u', '--user', nargs=1, required=True, help='The username with which to connect to the server', dest='username', type=str) 76 | parser.add_argument('-v', '--verbose', required=False, help='Enable verbose output', dest='verbose', action='store_true') 77 | parser.add_argument('-w', '--wait-max', nargs=1, required=False, help='Maximum amount of seconds to wait when gathering information (default 120)', dest='maxwait', type=int, default=[120]) 78 | 79 | args = parser.parse_args() 80 | 81 | ipv6 = args.ipv6 82 | amount = args.amount[0] 83 | basename = args.basename[0] 84 | count = args.count[0] 85 | folder = None 86 | if args.folder: 87 | folder = args.folder[0] 88 | post_script = None 89 | if args.post_script: 90 | post_script = args.post_script[0] 91 | resource_pool = None 92 | if args.resource_pool: 93 | resource_pool = args.resource_pool[0] 94 | server = args.server[0] 95 | template = args.template[0] 96 | username = args.username[0] 97 | verbose = args.verbose 98 | maxwait = args.maxwait[0] 99 | 100 | # Asking Users password for server 101 | password=getpass.getpass(prompt='Enter password for vCenter %s for user %s: ' % (server,username)) 102 | 103 | # Connecting to server 104 | print_verbose('Connecting to server %s with username %s' % (server,username)) 105 | con = VIServer() 106 | con.connect(server,username,password) 107 | print_verbose('Connected to server %s' % server) 108 | print_verbose('Server type: %s' % con.get_server_type()) 109 | print_verbose('API version: %s' % con.get_api_version()) 110 | 111 | # Verify the template exists 112 | print_verbose('Finding template %s' % template) 113 | template_vm = find_vm(template) 114 | if template_vm is None: 115 | print 'ERROR: %s not found' % template 116 | sys.exit(1) 117 | print_verbose('Template %s found' % template) 118 | 119 | # Verify the target Resource Pool exists 120 | resource_pool_mor = None 121 | if resource_pool is not None: 122 | print_verbose('Finding resource pool %s' % resource_pool) 123 | resource_pool_mor = find_resource_pool(resource_pool) 124 | if resource_pool_mor is None: 125 | print 'ERROR: %s not found' % resource_pool 126 | sys.exit(1) 127 | print_verbose('Resource pool %s found' % resource_pool) 128 | 129 | # Verify the target folder exists 130 | folder_mor = None 131 | if folder is not None: 132 | print_verbose('Finding folder %s' % folder) 133 | folder_mor = find_folder(folder) 134 | if folder_mor is None: 135 | print 'ERROR: %s not found' % folder 136 | sys.exit(1) 137 | print_verbose('Folder %s found' % folder) 138 | 139 | # List with VM name elements for post script processing 140 | vms_to_ps = [] 141 | # Looping through amount that needs to be created 142 | for a in range(1,amount+1): 143 | print_verbose('================================================================================') 144 | vm_name = '%s-%i' % (basename,count) 145 | print_verbose('Trying to clone %s to VM %s' % (template,vm_name)) 146 | if find_vm(vm_name): 147 | print 'ERROR: %s already exists' % vm_name 148 | else: 149 | clone = template_vm.clone(vm_name, True, folder_mor, resource_pool_mor, None, None, False) 150 | print_verbose('VM %s created' % vm_name) 151 | 152 | print_verbose('Booting VM %s' % vm_name) 153 | clone.power_on() 154 | 155 | if post_script: 156 | vms_to_ps.append(vm_name) 157 | count += 1 158 | 159 | # Looping through post scripting if necessary 160 | if post_script: 161 | for name in vms_to_ps: 162 | vm = find_vm(name) 163 | if vm: 164 | ip = find_ip(vm,ipv6) 165 | if ip: 166 | run_post_script(name,ip) 167 | else: 168 | print 'ERROR: No IP found for VM %s, post processing disabled' % name 169 | else: 170 | print 'ERROR: VM %s not found, post processing disabled' % name 171 | 172 | # Disconnecting from server 173 | con.disconnect() 174 | -------------------------------------------------------------------------------- /random-vmotion.py: -------------------------------------------------------------------------------- 1 | """ 2 | random-vmotion is a Python script which takes a list of VMs and a list of Hosts and will vMotion VMs randomly between those hosts per a provided interval. It will continue to do so until you stop it. 3 | 4 | This script has the following capabilities: 5 | * vMotion VMs to a random host 6 | * Continue until stopped 7 | * Print logging to a log file or stdout 8 | * Do this threaded 9 | 10 | --- Usage --- 11 | Run 'random-vmotion.py -h' for an overview 12 | 13 | --- Using threads --- 14 | Deciding on the optimal amount of threads might need a bit of experimentation. Keep certain things in mind: 15 | * The optimal amount of threads depends on the memory consumption of the VMs, the activity of the VMs and the amount of hosts as each thread will execute a vMotion task. If this is all to the same host with the a lot of activity in the VM, you might get in trouble. 16 | 17 | --- Files --- 18 | The files are a list of VMs and Hosts, each in a seperate file and with one entry per line 19 | 20 | --- Documentation --- 21 | https://github.com/pdellaert/vSphere-Python/blob/master/docs/random-vmotion.md 22 | 23 | --- Author --- 24 | Philippe Dellaert 25 | 26 | --- License --- 27 | https://raw.github.com/pdellaert/vSphere-Python/master/LICENSE.md 28 | 29 | """ 30 | 31 | from builtins import str 32 | import argparse 33 | import atexit 34 | import csv 35 | import getpass 36 | import logging 37 | import os.path 38 | import random 39 | 40 | from time import sleep 41 | from pyVim.connect import SmartConnect, SmartConnectNoSSL, Disconnect 42 | from pyVmomi import vim, vmodl 43 | from multiprocessing.dummy import Pool as ThreadPool 44 | 45 | 46 | def get_args(): 47 | """ 48 | Supports the command-line arguments listed below. 49 | """ 50 | 51 | parser = argparse.ArgumentParser(description="Randomly vMotion each VM from a list one by one to a random host from a list, until stopped.") 52 | parser.add_argument('-1', '--one-run', required=False, help='Stop after vMotioning each VM once', dest='onerun', action='store_true') 53 | parser.add_argument('-d', '--debug', required=False, help='Enable debug output', dest='debug', action='store_true') 54 | parser.add_argument('-H', '--host', nargs=1, required=True, help='The vCenter or ESXi host to connect to', dest='host', type=str) 55 | parser.add_argument('-i', '--interval', nargs=1, required=False, help='The amount of time to wait after a vMotion is finished to schedule a new one (default 30 seconds)', dest='interval', type=int, default=[30]) 56 | parser.add_argument('-l', '--log-file', nargs=1, required=False, help='File to log to (default = stdout)', dest='logfile', type=str) 57 | parser.add_argument('-o', '--port', nargs=1, required=False, help='Server port to connect to (default = 443)', dest='port', type=int, default=[443]) 58 | parser.add_argument('-p', '--password', nargs=1, required=False, help='The password with which to connect to the host. If not specified, the user is prompted at runtime for a password', dest='password', type=str) 59 | parser.add_argument('-S', '--disable-SSL-certificate-verification', required=False, help='Disable SSL certificate verification on connect', dest='nosslcheck', action='store_true') 60 | parser.add_argument('-t', '--targets', nargs=1, required=True, help='File with the list of target hosts to vMotion to', dest='targetfile', type=str) 61 | parser.add_argument('-T', '--threads', nargs=1, required=False, help='Amount of simultanious vMotions to execute at once. (default = 1)', dest='threads', type=int, default=[1]) 62 | parser.add_argument('-u', '--user', nargs=1, required=True, help='The username with which to connect to the host', dest='username', type=str) 63 | parser.add_argument('-v', '--verbose', required=False, help='Enable verbose output', dest='verbose', action='store_true') 64 | parser.add_argument('-V', '--vms', nargs=1, required=True, help='File with the list of VMs to vMotion', dest='vmfile', type=str) 65 | args = parser.parse_args() 66 | return args 67 | 68 | 69 | def vm_vmotion_handler(si, logger, vm, host, interval): 70 | """ 71 | Will handle the thread handling to vMotion a virtual machine 72 | """ 73 | 74 | logger.debug('THREAD %s - started' % vm.name) 75 | 76 | # Getting resource pool 77 | resource_pool = vm.resourcePool 78 | 79 | # Checking powerstate 80 | if vm.runtime.powerState != 'poweredOn': 81 | logger.warning('THREAD %s - VM is not powered on, vMotion is only available for powered on VMs.' % vm.name) 82 | return 0 83 | 84 | # Setting migration priority 85 | migrate_priority = vim.VirtualMachine.MovePriority.defaultPriority 86 | 87 | # Starting migration 88 | logger.debug('THREAD %s - Starting migration to host %s' % (vm.name, host.name)) 89 | migrate_task = vm.Migrate(pool=resource_pool, host=host, priority=migrate_priority) 90 | 91 | run_loop = True 92 | while run_loop: 93 | info = migrate_task.info 94 | logger.debug('THREAD %s - Checking vMotion task' % vm.name) 95 | if info.state == vim.TaskInfo.State.success: 96 | logger.debug('THREAD %s - vMotion finished' % vm.name) 97 | run_loop = False 98 | break 99 | elif info.state == vim.TaskInfo.State.running: 100 | logger.debug('THREAD %s - vMotion task is at %s percent' % (vm.name, info.progress)) 101 | elif info.state == vim.TaskInfo.State.queued: 102 | logger.debug('THREAD %s - vMotion task is queued' % vm.name) 103 | elif info.state == vim.TaskInfo.State.error: 104 | if info.error.fault: 105 | logger.info('THREAD %s - vMotion task has quit with error: %s' % (vm.name, info.error.fault.faultMessage)) 106 | else: 107 | logger.info('THREAD %s - vMotion task has quit with cancelation' % vm.name) 108 | run_loop = False 109 | break 110 | logger.debug('THREAD %s - Sleeping 1 second for new check' % vm.name) 111 | sleep(1) 112 | 113 | logger.debug('THREAD %s - Waiting %s seconds (interval) before ending the thread and releasing it for a new task' % (vm.name, interval)) 114 | sleep(interval) 115 | 116 | 117 | def wait_for_pool_end(logger, pool, pool_results): 118 | """ 119 | Waits for all running tasks to end. 120 | """ 121 | 122 | logger.debug('Waiting for %s vMotions to finish' % len(pool_results)) 123 | for result in pool_results: 124 | result.wait() 125 | pool.close() 126 | pool.join() 127 | 128 | 129 | def main(): 130 | """ 131 | Clone a VM or template into multiple VMs with logical names with numbers and allow for post-processing 132 | """ 133 | 134 | # Handling arguments 135 | args = get_args() 136 | onerun = args.onerun 137 | debug = args.debug 138 | host = args.host[0] 139 | interval = args.interval[0] 140 | log_file = None 141 | if args.logfile: 142 | log_file = args.logfile[0] 143 | port = args.port[0] 144 | password = None 145 | if args.password: 146 | password = args.password[0] 147 | nosslcheck = args.nosslcheck 148 | targetfile = args.targetfile[0] 149 | threads = args.threads[0] 150 | username = args.username[0] 151 | verbose = args.verbose 152 | vmfile = args.vmfile[0] 153 | 154 | # Logging settings 155 | if debug: 156 | log_level = logging.DEBUG 157 | elif verbose: 158 | log_level = logging.INFO 159 | else: 160 | log_level = logging.WARNING 161 | 162 | if log_file: 163 | logging.basicConfig(filename=log_file, format='%(asctime)s %(levelname)s %(message)s', level=log_level) 164 | else: 165 | logging.basicConfig(filename=log_file, format='%(asctime)s %(levelname)s %(message)s', level=log_level) 166 | logger = logging.getLogger(__name__) 167 | 168 | # Getting user password 169 | if password is None: 170 | logger.debug('No command line password received, requesting password from user') 171 | password = getpass.getpass(prompt='Enter password for vCenter %s for user %s: ' % (host, username)) 172 | 173 | try: 174 | si = None 175 | try: 176 | logger.info('Connecting to server %s:%s with username %s' % (host, port, username)) 177 | if nosslcheck: 178 | si = SmartConnectNoSSL(host=host, user=username, pwd=password, port=int(port)) 179 | else: 180 | si = SmartConnect(host=host, user=username, pwd=password, port=int(port)) 181 | except IOError as e: 182 | pass 183 | 184 | if not si: 185 | logger.error('Could not connect to host %s with user %s and specified password' % (host, username)) 186 | return 1 187 | 188 | logger.debug('Registering disconnect at exit') 189 | atexit.register(Disconnect, si) 190 | 191 | # Handling vms file 192 | logger.debug('Parsing VMs %s' % vmfile) 193 | 194 | if not os.path.isfile(vmfile): 195 | logger.critical('VM file %s does not exist, exiting' % vmfile) 196 | return 1 197 | 198 | # Getting VMs 199 | vms = [] 200 | content = si.content 201 | obj_view = content.viewManager.CreateContainerView(content.rootFolder, [vim.VirtualMachine], True) 202 | vm_list = obj_view.view 203 | with open(vmfile, 'rb') as tasklist: 204 | taskreader = csv.reader(tasklist, delimiter=';', quotechar="'") 205 | for row in taskreader: 206 | logger.debug('Found CSV row: %s' % ','.join(row)) 207 | # VM Name 208 | if row[0] is None or row[0] is '': 209 | logger.warning('No VM name specified, skipping this vm') 210 | continue 211 | else: 212 | cur_vm_name = row[0] 213 | 214 | # Finding VM 215 | found_vm = False 216 | for vm in vm_list: 217 | if vm.name == cur_vm_name: 218 | logger.debug('Found VM %s' % cur_vm_name) 219 | vms.append(vm) 220 | found_vm = True 221 | # Removing VM out of the list to speed up further lookups 222 | vm_list.remove(vm) 223 | break 224 | 225 | if not found_vm: 226 | logger.warning('VM %s does not exist, skipping this vm' % cur_vm_name) 227 | 228 | # Getting hosts 229 | obj_view = content.viewManager.CreateContainerView(content.rootFolder, [vim.HostSystem], True) 230 | host_list = obj_view.view 231 | hosts = [] 232 | with open(targetfile, 'rb') as tasklist: 233 | taskreader = csv.reader(tasklist, delimiter=';', quotechar="'") 234 | for row in taskreader: 235 | logger.debug('Found CSV row: %s' % ','.join(row)) 236 | # Host Name 237 | if row[0] is None or row[0] is '': 238 | logger.warning('No host name specified, skipping this host') 239 | continue 240 | else: 241 | cur_host_name = row[0] 242 | 243 | found_host = False 244 | for host in host_list: 245 | if host.name == cur_host_name: 246 | logger.debug('Found Host %s' % cur_host_name) 247 | hosts.append(host) 248 | found_host = True 249 | # Removing Host out of the list to speed up further lookups 250 | host_list.remove(host) 251 | break 252 | 253 | if not found_host: 254 | logger.warning('Host %s does not exist, skipping this host' % cur_host_name) 255 | 256 | if len(vms) < threads: 257 | logger.warning('Amount of threads %s can not be higher than amount of vms: Setting amount of threads to %s' % (threads, len(vms))) 258 | threads = len(vms) 259 | 260 | # Pool handling 261 | logger.debug('Setting up pools and threads') 262 | pool = ThreadPool(threads) 263 | pool_results = [] 264 | logger.debug('Pools created with %s threads' % threads) 265 | 266 | vm_index = 0 267 | run_loop = True 268 | while run_loop: 269 | # Check if a pool_result is finished 270 | for result in pool_results: 271 | if result.ready(): 272 | logger.debug('Removing finished task from the pool results') 273 | pool_results.remove(result) 274 | 275 | # If the pool is still filled, continue 276 | if len(pool_results) >= threads: 277 | logger.debug('All threads running, not creating new vMotion tasks. Waiting 1 second to check again') 278 | sleep(1) 279 | continue 280 | 281 | # If not, create new task (selects next VM, selects random host) 282 | vm = vms[vm_index] 283 | host = random.choice(hosts) 284 | logger.info('Creating vMotion task for VM %s to host %s' % (vm.name, host.name)) 285 | pool_results.append(pool.apply_async(vm_vmotion_handler, (si, logger, vm, host, interval))) 286 | 287 | vm_index += 1 288 | if vm_index >= len(vms) and onerun: 289 | logger.debug('One-run is enabled, all VMs are scheduled to vMotion. Finishing.') 290 | wait_for_pool_end(logger, pool, pool_results) 291 | run_loop = False 292 | break 293 | 294 | if vm_index >= len(vms): 295 | logger.debug('Looping back to first VM') 296 | vm_index = 0 297 | 298 | except KeyboardInterrupt: 299 | logger.info('Received interrupt, finishing running threads and not creating any new migrations') 300 | if pool is not None and pool_results is not None: 301 | wait_for_pool_end(logger, pool, pool_results) 302 | 303 | except vmodl.MethodFault as e: 304 | logger.critical('Caught vmodl fault: %s' % e.msg) 305 | return 1 306 | except Exception as e: 307 | logger.critical('Caught exception: %s' % str(e)) 308 | return 1 309 | 310 | logger.info('Finished all tasks') 311 | return 0 312 | 313 | # Start program 314 | if __name__ == "__main__": 315 | main() 316 | --------------------------------------------------------------------------------