Init commit

master
Jonathan Senkerik 2017-05-03 16:00:03 -04:00
commit 390d04b57b
3 changed files with 594 additions and 0 deletions

136
README.md 100644
View File

@ -0,0 +1,136 @@
Introduction
------------
This utility is a simple to use comand line tool to create VMs on an ESXi host from from a system running python and ssh. vCenter is not required. You need to enable ssh access on your ESXi server. It's HIGHLY RECOMMENDED to use password-less authentication by copying your ssh public keys to the ESXi host.
Command Line Args
-----------------
```
./esxi-vm-create -h
usage: esxi-vm-create [-h] [-d] [-v] [-H HOST] [-U USER] [-P PASSWORD]
[-n NAME] [-c CPU] [-m MEM] [-s SIZE] [-i ISO] [-N NET]
[-S STORE] [-g GUESTOS] [-u]
ESXi Create VM utility.
optional arguments:
-h, --help show this help message and exit
-d, --dry Enable Dry Run mode (False)
-v, --verbose Enable Verbose mode (False)
-H HOST, --Host HOST ESXi Host (esxi)
-U USER, --User USER ESXi Host username (root)
-P PASSWORD, --Password PASSWORD
ESXi Host password (*****)
-n NAME, --name NAME VM name
-c CPU, --cpu CPU Number of vCPUS (2)
-m MEM, --mem MEM Memory in GB (4)
-s SIZE, --size SIZE Size of virt disk (20)
-i ISO, --iso ISO CDROM ISO Path | None (None)
-N NET, --net NET Network Interface | None (None)
-S STORE, --store STORE
vmfs Store | LeastUsed (DS_3TB_m)
-g GUESTOS, --guestos GUESTOS
Guest OS. (centos-64)
-u, --updateDefaults Update Default VM settings stored in ~/.esxi-vm.yml
```
Examples
--------
Create a new VM named testvm01 using all defaults from ~/.esxi-vm.yml.
```
./esxi-vm-create -n testvm01
Create VM Success
ESXi Host: esxi
VM NAME: testvm01
vCPU: 2
Memory: 4GB
VM Disk: 20GB
DS Store: DS_4TB
Network: None
```
Change default number of vCPUs to 4, Memory to 8GB and vDisk size to 40GB.
```
./esxi-vm-create -c 4 -m 8 -s 40 -u
Saving new Defaults to ~/.esxi-vm.yml
```
Create a new VM named testvm02 using new defaults from ~/.esxi-vm.yml and specifying a Network interface.
```
./esxi-vm-create -n testvm02 -N 192.168.1
Create VM Success
ESXi Host: esxi
VM NAME: testvm02
vCPU: 4
Memory: 8GB
VM Disk: 40GB
DS Store: DS_4TB
Network: 192.168.1
```
Available Network Interfaces and Available Disk Storage volumes will be listed if an invalid entry is made.
```
./esxi-vm-create -n testvm03 -N BadNet -S BadDS
ERROR: Disk Storage BadDS doesn't exist.
Available Disk Stores: ['DS_SSD500s', 'DS_SSD500c', 'DS_SSD250', 'DS_4TB', 'DS_3TB_m']
LeastUsed Disk Store : DS_4TB
ERROR: Virtual NIC BadNet doesn't exist.
Available VM NICs: ['192.168.1', '192.168.0', 'VM Network test'] or 'None'
```
Create a new VM named testvm03 using a valid Network Interface, valid Disk Storage volume, enabled verbose and saving the settings as default.
```
./esxi-vm-create -n testvm03 -N 192.168.1 -S DS_3TB_m -v -u
Saving new Defaults to ~/.esxi-vm.yml
Create testvm03.vmx file
Create testvm03.vmdk file
Register VM
Power ON VM
Create VM Success
ESXi Host: esxi
VM NAME: testvm03
vCPU: 4
Memory: 8GB
VM Disk: 40GB
Format: thin
DS Store: DS_3TB_m
Network: 192.168.1
Guest OS: centos-64
```
License
-------
Copyright (C) 2017 Jonathan Senkerik
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Support
-------
Website : http://www.jintegrate.co
github : http://github.com/josenk/

369
esxi-vm-create 100755
View File

@ -0,0 +1,369 @@
#!/usr/bin/python
import argparse # Argument parser
import datetime # For current Date/Time
import os.path # To check if file exists
import sys # For args
import re # For regex
import paramiko # For remote ssh
import yaml
import warnings
from esxi_vm_functions import *
# Defaults and Variable setup
ConfigData = setup_config()
NAME = ""
LOG = ConfigData['LOG']
isDryRun = ConfigData['isDryRun']
isVerbose = ConfigData['isVerbose']
HOST = ConfigData['HOST']
USER = ConfigData['USER']
PASSWORD = ConfigData['PASSWORD']
CPU = ConfigData['CPU']
MEM = ConfigData['MEM']
SIZE = int(ConfigData['SIZE'])
DISKFORMAT = ConfigData['DISKFORMAT']
VIRTDEV = ConfigData['VIRTDEV']
STORE = ConfigData['STORE']
NET = ConfigData['NET']
ISO = ConfigData['ISO']
GUESTOS = ConfigData['GUESTOS']
#
# Process Arguments
#
parser = argparse.ArgumentParser(description='ESXi Create VM utility.')
parser.add_argument('-d', '--dry', dest='isDryRunarg', action='store_true', help="Enable Dry Run mode (" + str(isDryRun) + ")")
parser.add_argument('-v', '--verbose', dest='isVerbosearg', action='store_true', help="Enable Verbose mode (" + str(isVerbose) + ")")
parser.add_argument("-H", "--Host", dest='HOST', type=str, help="ESXi Host (" + str(HOST) + ")")
parser.add_argument("-U", "--User", dest='USER', type=str, help="ESXi Host username (" + str(USER) + ")")
parser.add_argument("-P", "--Password", dest='PASSWORD', type=str, help="ESXi Host password (*****)")
parser.add_argument("-n", "--name", dest='NAME', type=str, help="VM name")
parser.add_argument("-c", "--cpu", dest='CPU', type=int, help="Number of vCPUS (" + str(CPU) + ")")
parser.add_argument("-m", "--mem", type=int, help="Memory in GB (" + str(MEM) + ")")
parser.add_argument("-s", "--size", dest='SIZE', type=str, help="Size of virt disk (" + str(SIZE) + ")")
parser.add_argument("-i", "--iso", dest='ISO', type=str, help="CDROM ISO Path | None (" + str(ISO) + ")")
parser.add_argument("-N", "--net", dest='NET', type=str, help="Network Interface | None (" + str(NET) + ")")
parser.add_argument("-S", "--store", dest='STORE', type=str, help="vmfs Store | LeastUsed (" + str(STORE) + ")")
parser.add_argument("-g", "--guestos", dest='GUESTOS', type=str, help="Guest OS. (" + str(GUESTOS) + ")")
parser.add_argument("-u", "--updateDefaults", dest='UPDATE', action='store_true', help="Update Default VM settings stored in ~/.esxi-vm.yml")
args = parser.parse_args()
if args.isDryRunarg:
isDryRun = True
if args.isVerbosearg:
isVerbose = True
if args.HOST:
HOST=args.HOST
if args.USER:
USER=args.USER
if args.PASSWORD:
PASSWORD=args.PASSWORD
if args.NAME:
NAME=args.NAME
if args.CPU:
CPU=int(args.CPU)
if args.mem:
MEM=int(args.mem)
if args.SIZE:
SIZE=int(args.SIZE)
if args.ISO:
ISO=args.ISO
if args.NET:
NET=args.NET
if args.STORE:
STORE=args.STORE
if STORE == "":
STORE = "LeastUsed"
if args.GUESTOS:
GUESTOS=args.GUESTOS
if args.UPDATE:
print "Saving new Defaults to ~/.esxi-vm.yml"
ConfigData['isDryRun'] = isDryRun
ConfigData['isVerbose'] = isVerbose
ConfigData['HOST'] = HOST
ConfigData['USER'] = USER
ConfigData['PASSWORD'] = PASSWORD
ConfigData['CPU'] = CPU
ConfigData['MEM'] = MEM
ConfigData['SIZE'] = SIZE
ConfigData['DISKFORMAT'] = DISKFORMAT
ConfigData['VIRTDEV'] = VIRTDEV
ConfigData['STORE'] = STORE
ConfigData['NET'] = NET
ConfigData['ISO'] = ISO
ConfigData['GUESTOS'] = GUESTOS
SaveConfig(ConfigData)
if NAME == "":
sys.exit(0)
#
# main()
#
# print "Current Date " + theCurrDateTime()
CheckHasErrors = False
if NAME == "":
print "ERROR: Missing required option --name"
sys.exit(1)
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USER, password=PASSWORD)
(stdin, stdout, stderr) = ssh.exec_command("esxcli system version get |grep Version")
type(stdin)
if re.match("Version", stdout.readlines()) is not None:
print "Unable to access ESXi Host: %s, username: %s" % (HOST, USER)
sys.exit(1)
except:
pass
#
# Get list of DataStores, store in VOLUMES
#
LeastUsedDS = ""
try:
(stdin, stdout, stderr) = ssh.exec_command("esxcli storage filesystem list |grep '/vmfs/volumes/.*true VMFS' |sort -nk7")
type(stdin)
VOLUMES = {}
for line in stdout.readlines():
splitLine = line.split()
VOLUMES[splitLine[0]] = splitLine[1]
LeastUsedDS = splitLine[1]
except:
e = sys.exc_info()[0]
print "The Error is " + str(e)
sys.exit(1)
if STORE == "LeastUsed":
STORE = LeastUsedDS
#
# Get list of Networks available, store in VMNICS
#
try:
(stdin, stdout, stderr) = ssh.exec_command("esxcli network vswitch standard list|grep Portgroups|sed 's/^ Portgroups: //g'")
type(stdin)
VMNICS = []
for line in stdout.readlines():
splitLine = re.split(',|\n', line)
VMNICS.append(splitLine[0])
except:
e = sys.exc_info()[0]
print "The Error is " + str(e)
sys.exit(1)
#
# Get from ESXi host if ISO exists
#
ISOfound = False
if ISO == "None":
ISO = ""
if ISO != "":
try:
(stdin, stdout, stderr) = ssh.exec_command("ls " + str(ISO))
type(stdin)
if not stdout.readlines() and stderr.readlines():
ISOfound = True
except:
e = sys.exc_info()[0]
print "The Error is " + str(e)
sys.exit(1)
#
# Check if VM already exists
#
VMID = -1
try:
(stdin, stdout, stderr) = ssh.exec_command("vim-cmd vmsvc/getallvms")
type(stdin)
for line in stdout.readlines():
splitLine = line.split()
if NAME == splitLine[1]:
VMID = splitLine[0]
print "ERROR: VM " + NAME + " already exists."
CheckHasErrors = True
except:
e = sys.exc_info()[0]
print "The Error is " + str(e)
sys.exit(1)
#
# Do checks here
#
# Check CPU
if CPU < 1 or CPU > 128:
print str(CPU) + " CPU out of range. [1-128]"
CheckHasErrors = True
# Check MEM
if MEM < 1 or MEM > 4080:
print str(MEM) + "GB Memory out of range. [1-4080]"
CheckHasErrors = True
# Check SIZE
if SIZE < 1 or SIZE > 63488:
print "Virtual Disk size " + str(SIZE) + "GB out of range. [1-63488]"
CheckHasErrors = True
# Check STORE
V = []
DSPATH=""
DSSTORE=""
for Path in VOLUMES:
V.append(VOLUMES[Path])
if STORE == Path or STORE == VOLUMES[Path]:
DSPATH = Path
DSSTORE = VOLUMES[Path]
if DSSTORE not in V:
print "ERROR: Disk Storage " + STORE + " doesn't exist. "
print " Available Disk Stores: " + str(V)
print " LeastUsed Disk Store : " + str(LeastUsedDS)
CheckHasErrors = True
# Check NIC (NIC record)
if (NET not in VMNICS) and (NET != "None"):
print "ERROR: Virtual NIC " + NET + " doesn't exist."
print " Available VM NICs: " + str(VMNICS) + " or 'None'"
CheckHasErrors = True
# Check ISO exists
if ISO != "" and not ISOfound:
print "ERROR: ISO " + ISO + " not found. Use full path to ISO"
CheckHasErrors = True
# Check if DSPATH/NAME aready exists
FullPathExists = False
try:
FullPath = DSPATH + "/" + NAME
(stdin, stdout, stderr) = ssh.exec_command("ls -d " + FullPath)
type(stdin)
if stdout.readlines() and not stderr.readlines():
print "ERROR: Directory " + FullPath + " already exists."
CheckHasErrors = True
except:
pass
#
# Exit if there are any errors
#
if CheckHasErrors:
sys.exit(1)
#
# Create the VM
#
VMX = []
VMX.append('config.version = "8"')
VMX.append('virtualHW.version = "8"')
VMX.append('vmci0.present = "TRUE"')
VMX.append('displayName = "' + NAME + '"')
VMX.append('floppy0.present = "FALSE"')
VMX.append('numvcpus = "' + str(CPU) + '"')
VMX.append('scsi0.present = "TRUE"')
VMX.append('scsi0.sharedBus = "none"')
VMX.append('scsi0.virtualDev = "pvscsi"')
VMX.append('memsize = "' + str(MEM * 1024) + '"')
VMX.append('scsi0:0.present = "TRUE"')
VMX.append('scsi0:0.fileName = "' + NAME + '.vmdk"')
VMX.append('scsi0:0.deviceType = "scsi-hardDisk"')
if ISO == "":
VMX.append('ide1:0.present = "TRUE"')
VMX.append('ide1:0.fileName = "emptyBackingString"')
VMX.append('ide1:0.deviceType = "atapi-cdrom"')
VMX.append('ide1:0.startConnected = "FALSE"')
VMX.append('ide1:0.clientDevice = "TRUE"')
else:
VMX.append('ide1:0.present = "TRUE"')
VMX.append('ide1:0.fileName = "' + ISO + '"')
VMX.append('ide1:0.deviceType = "cdrom-image"')
VMX.append('pciBridge0.present = "TRUE"')
VMX.append('pciBridge4.present = "TRUE"')
VMX.append('pciBridge4.virtualDev = "pcieRootPort"')
VMX.append('pciBridge4.functions = "8"')
VMX.append('pciBridge5.present = "TRUE"')
VMX.append('pciBridge5.virtualDev = "pcieRootPort"')
VMX.append('pciBridge5.functions = "8"')
VMX.append('pciBridge6.present = "TRUE"')
VMX.append('pciBridge6.virtualDev = "pcieRootPort"')
VMX.append('pciBridge6.functions = "8"')
VMX.append('pciBridge7.present = "TRUE"')
VMX.append('pciBridge7.virtualDev = "pcieRootPort"')
VMX.append('pciBridge7.functions = "8"')
VMX.append('guestOS = "' + GUESTOS + '"')
if NET != "None":
VMX.append('ethernet0.virtualDev = "vmxnet3"')
VMX.append('ethernet0.present = "TRUE"')
VMX.append('ethernet0.networkName = "' + NET + '"')
VMX.append('ethernet0.addressType = "generated"')
if isDryRun:
print "Dry Run Enabled. No VM created..."
sys.exit(0)
else:
try:
# Create NAME.vmx
if isVerbose:
print "Create " + NAME + ".vmx file"
MyVM = FullPath + "/" + NAME
(stdin, stdout, stderr) = ssh.exec_command("mkdir " + FullPath )
type(stdin)
for line in VMX:
(stdin, stdout, stderr) = ssh.exec_command("echo " + line + " >>" + MyVM + ".vmx")
type(stdin)
# Create vmdk
if isVerbose:
print "Create " + NAME + ".vmdk file"
(stdin, stdout, stderr) = ssh.exec_command("vmkfstools -c " + str(SIZE) + "G -d " + DISKFORMAT + " " + MyVM + ".vmdk")
type(stdin)
# Register VM
if isVerbose:
print "Register VM"
(stdin, stdout, stderr) = ssh.exec_command("vim-cmd solo/registervm " + MyVM + ".vmx")
type(stdin)
Jnk = stdout.readlines()
VMID = int(Jnk[0])
# Power on VM
if isVerbose:
print "Power ON VM"
(stdin, stdout, stderr) = ssh.exec_command("vim-cmd vmsvc/power.on " + str(VMID))
type(stdin)
if stderr.readlines():
print "Error Power.on VM."
sys.exit(1)
except:
print "There was an error creating the VM!"
sys.exit(1)
# Print Summary
print "\nCreate VM Success"
print "ESXi Host: " + HOST
print "VM NAME: " + NAME
print "vCPU: " + str(CPU)
print "Memory: " + str(MEM) + "GB"
print "VM Disk: " + str(SIZE) + "GB"
if isVerbose:
print "Format: " + DISKFORMAT
print "DS Store: " + DSSTORE
print "Network: " + NET
if ISO:
print "ISO: " + ISO
if isVerbose:
print "Guest OS: " + GUESTOS

View File

@ -0,0 +1,89 @@
import os.path
import yaml
import datetime # For current Date/Time
import paramiko # For remote ssh
from math import log
#
#
# Functions
#
#
def setup_config():
#
# System wide defaults
#
ConfigData = dict(
LOG="~/esxi-vm.log",
isDryRun=False,
isVerbose=False,
HOST="esxi",
USER="root",
PASSWORD="",
CPU=2,
MEM=4,
SIZE=20,
DISKFORMAT="thin",
VIRTDEV="pvscsi",
STORE="LeastUsed",
NET="None",
ISO="None",
GUESTOS="centos-64"
)
ConfigDataFileLocation = os.path.expanduser("~") + "/.esxi-vm.yml"
#
# Get ConfigData from ConfigDataFile, then merge.
if os.path.exists(ConfigDataFileLocation):
FromFileConfigData = yaml.safe_load(open(ConfigDataFileLocation))
ConfigData.update(FromFileConfigData)
try:
with open(ConfigDataFileLocation, 'w') as FD:
yaml.dump(ConfigData, FD, default_flow_style=False)
FD.close()
except:
print "Unable to create/update config file " + ConfigDataFileLocation
e = sys.exc_info()[0]
print "The Error is " + str(e)
sys.exit(1)
return ConfigData
def SaveConfig(ConfigData):
ConfigDataFileLocation = os.path.expanduser("~") + "/.esxi-vm.yml"
try:
with open(ConfigDataFileLocation, 'w') as FD:
yaml.dump(ConfigData, FD, default_flow_style=False)
FD.close()
except:
print "Unable to create/update config file " + ConfigDataFileLocation
e = sys.exc_info()[0]
print "The Error is " + str(e)
return 1
return 0
def theCurrDateTime():
i = datetime.datetime.now()
return str(i.isoformat())
unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2])
def float2human(num):
"""Integer to Human readable"""
if num > 1:
exponent = min(int(log(float(num), 1024)), len(unit_list) - 1)
quotient = float(num) / 1024**exponent
unit, num_decimals = unit_list[exponent]
format_string = '{:.%sf} {}' % (num_decimals)
return format_string.format(quotient, unit)
if num == 0:
return '0 bytes'
if num == 1:
return '1 byte'