Skip to content

Instantly share code, notes, and snippets.

@braoru
Created October 20, 2016 09:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save braoru/946650dc501468e0e1b942c95273d663 to your computer and use it in GitHub Desktop.
Save braoru/946650dc501468e0e1b942c95273d663 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
# Copyright (C) 2015-:
# Pasche Sebastien, sebastien.pasche@leshop.ch
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
pvomni.wrapper
~~~~~~~~~~~~~
A vmware pyvmomi helper and wrapper for general and simple
vmware related manipulations
"""
from __future__ import unicode_literals
import logging
import time
import requests
import ssl
import base64
from pyVim import connect
from pyVmomi import vmodl, vim, pyVmomi
from hurricane.hypervisors.factory import Hypverisor
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("hurricane.hypervisors")
class PyVmomiDriver:
#Mapping to vmware nic type from type name
_name_nic_type = {
"virtualvmxnet" : vim.vm.device.VirtualVmxnet(),
"virtualvmxnet2": vim.vm.device.VirtualVmxnet2(),
"virtualvmxnet3": vim.vm.device.VirtualVmxnet3()
}
@classmethod
def create_nic_from_type_name(cls, nic_type_name):
"""
Return a vmware vim nic instance
:param nic_type_name: name of the nic instance to obtain
:return: An instance of the nic vim object
"""
nic_name = str(nic_type_name).lower()
if nic_name not in cls._name_nic_type:
raise TypeError(
"{n} is not a supported vmware nic type".format(
n=nic_name
)
)
return cls._name_nic_type.get(nic_name)
class PyVmomiHelper:
##Moved to driver
@classmethod
def create_nic_from_type_name(cls, nic_type_name):
if str(nic_type_name).lower() == 'virtualvmxnet':
return vim.vm.device.VirtualVmxnet()
elif str(nic_type_name).lower() == 'virtualvmxnet2':
return vim.vm.device.VirtualVmxnet2()
elif str(nic_type_name).lower() == 'virtualvmxnet3':
return vim.vm.device.VirtualVmxnet3()
@classmethod
def find_mac_address(cls, vm):
for device in vm.config.hardware.device:
if isinstance(device, vim.vm.device.VirtualEthernetCard):
return device.macAddress
return None
@classmethod
def find_cluster(cls, vcenter_service, name):
content = vcenter_service.content
obj_view = content.viewManager.CreateContainerView(content.rootFolder, [vim.ClusterComputeResource], True)
cluster_list = obj_view.view
obj_view.Destroy()
for cluster in cluster_list:
if cluster.name == name:
return cluster
return None
@classmethod
def find_folder(cls, vcenter_service, name):
content = vcenter_service.content
obj_view = content.viewManager.CreateContainerView(content.rootFolder, [vim.Folder], True)
folder_list = obj_view.view
obj_view.Destroy()
for folder in folder_list:
if folder.name == name:
return folder
return None
@classmethod
def find_vm_by_name(cls, service_instance, virtual_host_context):
""" Based on the vm_name, returns a VM instance """
virtual_host_name = virtual_host_context.virtual_host.hostname
vms_data = cls.collect_properties(
service_instance,
view_ref=cls.get_container_view(service_instance,
obj_type=[vim.VirtualMachine]),
obj_type=vim.VirtualMachine,
path_set=["name", "config.uuid"],
include_mors=True
)
for i, vm_props in enumerate(vms_data):
if vm_props["name"] == virtual_host_name:
return service_instance.content.searchIndex.FindByUuid(None, vm_props["config.uuid"], True)
return None
@classmethod
def _get_obj(cls, content, vimtype, name):
"""
Get the vsphere object associated with a given text name
"""
obj = None
container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True)
for c in container.view:
if c.name == name:
obj = c
break
return obj
@classmethod
def _get_all_objs(cls, content, vimtype):
"""
Get all the vsphere objects associated with a given type
"""
obj = {}
container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True)
for c in container.view:
obj.update({c: c.name})
return obj
@classmethod
def get_host_by_name(cls, si, name):
"""
Find an hypervisor host by it's name and return it
"""
return cls._get_obj(si.RetrieveContent(), [vim.HostSystem], name)
### ----------------------------------------------------------------------
# <community-samples>
### ----------------------------------------------------------------------
@classmethod
def collect_properties(cls, service_instance, view_ref, obj_type, path_set=None, include_mors=False):
"""
Collect properties for managed objects from a view ref
Check the vSphere API documentation for example on retrieving
object properties:
- http://goo.gl/erbFDz
Args:
si (ServiceInstance): ServiceInstance connection
view_ref (pyVmomi.vim.view.*): Starting point of inventory navigation
obj_type (pyVmomi.vim.*): Type of managed object
path_set (list): List of properties to retrieve
include_mors (bool): If True include the managed objects
refs in the result
Returns:
A list of properties for the managed objects
"""
collector = service_instance.content.propertyCollector
# Create object specification to define the starting point of
# inventory navigation
obj_spec = pyVmomi.vmodl.query.PropertyCollector.ObjectSpec()
obj_spec.obj = view_ref
obj_spec.skip = True
# Create a traversal specification to identify the path for collection
traversal_spec = pyVmomi.vmodl.query.PropertyCollector.TraversalSpec()
traversal_spec.name = "traverseEntities"
traversal_spec.path = "view"
traversal_spec.skip = False
traversal_spec.type = view_ref.__class__
obj_spec.selectSet = [traversal_spec]
# Identify the properties to the retrieved
property_spec = pyVmomi.vmodl.query.PropertyCollector.PropertySpec()
property_spec.type = obj_type
if not path_set:
property_spec.all = True
property_spec.pathSet = path_set
# Add the object and property specification to the
# property filter specification
filter_spec = pyVmomi.vmodl.query.PropertyCollector.FilterSpec()
filter_spec.objectSet = [obj_spec]
filter_spec.propSet = [property_spec]
# Retrieve properties
props = collector.RetrieveContents([filter_spec])
data = []
for obj in props:
properties = {}
for prop in obj.propSet:
properties[prop.name] = prop.val
if include_mors:
properties["obj"] = obj.obj
data.append(properties)
return data
@classmethod
def get_container_view(cls, service_instance, obj_type, container=None):
"""
Get a vSphere Container View reference to all objects of type "obj_type"
It is up to the caller to take care of destroying the View when no longer
needed.
Args:
obj_type (list): A list of managed object types
Returns:
A container view ref to the discovered managed objects
"""
if not container:
container = service_instance.content.rootFolder
view_ref = service_instance.content.viewManager.CreateContainerView(
container=container,
type=obj_type,
recursive=True
)
return view_ref
@classmethod
def get_vm_question(
cls,
vm
):
choices = vm.runtime.question.choice.choiceInfo
default_option = None
logger.info("VM is paused by a question")
logger.info(
"Question : {id} - {q}".format(
id=vm.runtime.question.id,
q=vm.runtime.question.text
)
)
if vm.runtime.question.choice.defaultIndex is not None:
index = vm.runtime.question.choice.defaultIndex
default_option = choices[index]
for option in choices:
logger.info(
"Choice : {k} - {l}".format(
k=option.key,
l=option.label
)
)
if default_option is not None:
logger.info(
"Default : {k} - {l}".format(
k=default_option.key,
l=default_option.label
)
)
question = {
"test": vm.runtime.question.text,
"id": vm.runtime.question.id,
'messages_types': [
msg.id for msg in vm.runtime.question.message
],
"choices": choices,
"default_choice": default_option
}
return question
@classmethod
def WaitTask(
cls,
task
):
""" Wait for the given task to be completed and returns result when done """
while task.info.state == vim.TaskInfo.State.running:
time.sleep(1)
return task.info.result
@classmethod
def wait_task(
cls,
vm=None,
task=None,
answers=None
):
""" Wait for the given task to be completed and returns result when done """
while task.info.state not in [
vim.TaskInfo.State.success,
vim.TaskInfo.State.error
]:
if vm is not None:
if vm.runtime.question is not None:
question = cls.get_vm_question(vm)
question_id = question['id']
known_questions_messages = list(
set(question['messages_types']).intersection(
set(
answers.keys()
)
)
)
if (
answers is not None and
len(known_questions_messages)
):
#I use the first element ..
#TODO : Find a better way
answer = answers[known_questions_messages[0]]
vm.AnswerVM(question_id, answer)
logger.info(
"Answer choosen : {k}".format(
k=answer
)
)
else:
if question['default_choice'] is not None:
default_option = question['default_choice']
vm.AnswerVM(question_id, default_option.key)
logger.info(
"Answer choosen : {k}".format(
k=default_option.key
)
)
else:
raise Exception("VM question don't have any answer")
time.sleep(1)
if task.info.state == vim.TaskInfo.State.error:
logger.error("Error type : {t}".format(t=task.info.error.__class__.__name__))
logger.error("Cause : {f}".format(f=task.info.error.faultCause))
for error in task.info.error.faultMessage:
logger.error(
"Error : {k} - {m}".format(
k=error.key,
m=error.message
)
)
return task.info.result
@classmethod
def create_hdd_controller(cls):
#
# Scsi controller instantiation and operation setup
#
hdd_controller_specs = vim.vm.device.VirtualDeviceSpec()
hdd_controller_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
hdd_controller_specs.device = vim.vm.device.ParaVirtualSCSIController()
hdd_controller_specs.device.key = 1000
hdd_controller_specs.device.controllerKey = 100
hdd_controller_specs.device.device = [2000]
#
# Device construction
# hdd_controller_specs.device.device: list of devices held by controller
#
hdd_controller_specs.device.sharedBus = "noSharing"
return hdd_controller_specs
@classmethod
def create_hdd(
cls,
disk_size_kb=10000,
is_thin_provisioned=False
):
hdd_specs = vim.vm.device.VirtualDeviceSpec()
hdd_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
hdd_specs.fileOperation = "create"
hdd_specs.device = vim.vm.device.VirtualDisk()
hdd_specs.device.key = 2000
#
# Hdd basic specs
#
hdd_specs.device.capacityInKB = disk_size_kb
hdd_specs.device.controllerKey = 1000
hdd_specs.device.unitNumber = 0
#
# sharing specs
#
hdd_specs.device.shares = vim.SharesInfo()
hdd_specs.device.shares.shares = 1000
hdd_specs.device.shares.level = "normal"
hdd_backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
hdd_backing.diskMode = vim.vm.device.VirtualDiskOption.DiskMode.persistent
if is_thin_provisioned:
hdd_backing.thinProvisioned=True
hdd_specs.device.backing = hdd_backing
return hdd_specs
@classmethod
def create_network_adapter(cls, nic_type, network):
network_adapter_specs = vim.vm.device.VirtualDeviceSpec()
network_adapter_specs.device = cls.create_nic_from_type_name(nic_type)
network_adapter_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
network_adapter_specs.device.key = 4000
network_adapter_specs.device.controllerKey = 100
network_adapter_backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
network_adapter_backing.inPassthroughMode = False
network_adapter_backing.deviceName = network
network_adapter_specs.device.backing = network_adapter_backing
network_adapter_specs.device.addressType = "assigned"
network_adapter_specs.device.wakeOnLanEnabled = True
return network_adapter_specs
@classmethod
def create_pci_controller(cls):
""" Simple pci controller configuration used for cdrom """
pci_controller_specs = vim.vm.device.VirtualDeviceSpec()
pci_controller_specs.device = vim.vm.device.VirtualPCIController()
pci_controller_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
pci_controller_specs.device.key = 100
pci_controller_specs.device.device = [4000]
pci_controller_specs.device.busNumber = 0
return pci_controller_specs
@classmethod
def create_cdrom_drive(cls):
""" Basic cdrom drive config """
virtual_cdrom_specs = vim.vm.device.VirtualDeviceSpec()
virtual_cdrom_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
virtual_cdrom_specs.device = vim.vm.device.VirtualCdrom()
virtual_cdrom_specs.device.controllerKey = 201
virtual_cdrom_specs.device.backing = vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
virtual_cdrom_specs.device.backing.exclusive = False
virtual_cdrom_specs.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
virtual_cdrom_specs.device.connectable.connected = False
virtual_cdrom_specs.device.connectable.startConnected = False
virtual_cdrom_specs.device.connectable.allowGuestControl = True
return virtual_cdrom_specs
@staticmethod
def mount_iso(iso_file_path):
""" Mount an iso file at the specified location """
virtual_cdrom_specs = vim.vm.device.VirtualDeviceSpec()
virtual_cdrom_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
virtual_cdrom_specs.device = vim.vm.device.VirtualCdrom()
# controllerKey is tied to IDE Controller
virtual_cdrom_specs.device.controllerKey = 201
# key is needed to mount the iso, need to verify if this value
# changes per host, and if so, then logic needs to be added to
# obtain it
virtual_cdrom_specs.device.key = 3002
virtual_cdrom_specs.device.backing = vim.vm.device.VirtualCdrom.IsoBackingInfo()
virtual_cdrom_specs.device.backing.fileName = iso_file_path
virtual_cdrom_specs.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
virtual_cdrom_specs.device.connectable.connected = True
virtual_cdrom_specs.device.connectable.startConnected = True
virtual_cdrom_specs.device.connectable.allowGuestControl = True
return virtual_cdrom_specs
@staticmethod
def unmount_iso():
""" Mount an iso file at the specified location """
virtual_cdrom_specs = vim.vm.device.VirtualDeviceSpec()
virtual_cdrom_specs.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
virtual_cdrom_specs.device = vim.vm.device.VirtualCdrom()
# controllerKey is tied to IDE Controller
virtual_cdrom_specs.device.controllerKey = 201
# key is needed to mount the iso, need to verify if this value
# changes per host, and if so, then logic needs to be added to
# obtain it
virtual_cdrom_specs.device.key = 3002
virtual_cdrom_specs.device.backing = vim.vm.device.VirtualCdrom.RemoteAtapiBackingInfo()
virtual_cdrom_specs.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
virtual_cdrom_specs.device.connectable.connected = False
virtual_cdrom_specs.device.connectable.startConnected = False
virtual_cdrom_specs.device.connectable.allowGuestControl = False
return virtual_cdrom_specs
@staticmethod
def add_note(note, vm):
note_spec = vim.vm.ConfigSpec()
note_spec.annotation = note
add_note_task = vm.ReconfigVM_Task(note_spec)
PyVmomiHelper.WaitTask(
vm=vm,
task=add_note_task
)
class PyVmomiHypervisor(Hypverisor):
@staticmethod
def create_connection(vcenter, user, password):
logger.info("Attempting to connect to vcenter...")
requests.packages.urllib3.disable_warnings()
#try:
# logger.info("Create ssl overhide")
# _create_unverified_https_context = ssl._create_unverified_context
#except AttributeError:
# logger.error("Can't overhide https context")
# Legacy Python that doesn't verify HTTPS certificates by default
#pass
#else:
# logger.info("Apply ssl overhide")
# Handle target environment that doesn't support HTTPS verification
# ssl._create_default_https_context = _create_unverified_https_context
try:
service = connect.SmartConnect(
host=vcenter,
user=user,
pwd=password,
port=443,
sslContext=ssl.SSLContext(ssl.PROTOCOL_TLSv1)
)
service.content.sessionManager.SetLocale('en_US')
logger.info("Connection to vcenter successfully established!")
# ssl._create_default_https_context()
return service
except vmodl.MethodFault as error:
logger.error("Connection to vcenter cannot be established {err}".format(err=error.msg))
return None
@staticmethod
def release_connection(connection):
connect.Disconnect(connection)
@staticmethod
def power_on(vcenter_con, virtual_host_context):
vm = PyVmomiHelper.find_vm_by_name(vcenter_con, virtual_host_context)
logger.info("powering on virtual machine")
power_on_task = vm.PowerOnVM_Task()
power_on_task_result = PyVmomiHelper.WaitTask(power_on_task)
return power_on_task_result
@staticmethod
def power_off(vcenter_service, virtual_host_context):
vm = PyVmomiHelper.find_vm_by_name(vcenter_service, virtual_host_context)
logger.info("powering on virtual machine")
power_on_task = vm.PowerOffVM_Task()
power_on_task_result = PyVmomiHelper.WaitTask(power_on_task)
return power_on_task_result
@staticmethod
def create_virtual_host(vcenter_service, virtual_host_context):
logger.info("preparing virtual host creation config...")
content = vcenter_service.RetrieveContent()
datacenter = content.rootFolder.childEntity[0]
hypervisor_host = PyVmomiHelper.get_host_by_name(vcenter_service, virtual_host_context.virtual_host.vmware.esx)
resource_pool = hypervisor_host.parent.resourcePool
is_disk_thin_provisioned = False
if virtual_host_context.virtual_host.vmware.virtual_devices.disk_provisioning == 'thin':
is_disk_thin_provisioned = True
devices_creation = [
PyVmomiHelper.create_hdd_controller(),
PyVmomiHelper.create_hdd(
disk_size_kb=virtual_host_context.virtual_host.disksize_kb,
is_thin_provisioned=is_disk_thin_provisioned
),
PyVmomiHelper.create_pci_controller(),
PyVmomiHelper.create_network_adapter(
virtual_host_context.virtual_host.vmware.virtual_devices.nic,
virtual_host_context.virtual_host.vmware.network
),
PyVmomiHelper.create_cdrom_drive(),
]
vmx_file = vim.vm.FileInfo(
logDirectory=None,
snapshotDirectory=None,
suspendDirectory=None,
vmPathName='[{ds}]{vm}'.format(
ds=virtual_host_context.virtual_host.vmware.datastore,
vm=virtual_host_context.virtual_host.hostname
)
)
vm_config = vim.vm.ConfigSpec(
name=virtual_host_context.virtual_host.hostname,
memoryMB=virtual_host_context.virtual_host.memory_mb,
numCPUs=virtual_host_context.virtual_host.vcpu,
guestId=vim.vm.GuestOsDescriptor.GuestOsIdentifier.otherLinux64Guest,
files=vmx_file,
version='vmx-08',
deviceChange=devices_creation
)
logger.debug("creation specs generated")
logger.debug("pushing creation specs to vcenter")
create_task = datacenter.vmFolder.CreateVM_Task(
config=vm_config,
pool=resource_pool,
host=hypervisor_host
)
PyVmomiHelper.WaitTask(
create_task
)
vm = PyVmomiHelper.find_vm_by_name(vcenter_service, virtual_host_context)
return PyVmomiHelper.find_mac_address(vm)
@staticmethod
def configure_virtual_host_specs(virtual_host_context, vcenter_service):
pass
@staticmethod
def mount_operating_system_installation_device(connection, virtual_host_context):
changes_spec = [
PyVmomiHelper.mount_iso(virtual_host_context.virtual_host.install.iso)
]
vm = PyVmomiHelper.find_vm_by_name(connection, virtual_host_context)
changes_spec = vim.vm.ConfigSpec(deviceChange=changes_spec)
reconfig_task = vm.ReconfigVM_Task(changes_spec)
PyVmomiHelper.WaitTask(reconfig_task)
@staticmethod
def unmount_operating_system_installation_device(connection, virtual_host_context):
remove_spec = [
PyVmomiHelper.unmount_iso()
]
vm = PyVmomiHelper.find_vm_by_name(connection, virtual_host_context)
changes_spec = vim.vm.ConfigSpec(deviceChange=remove_spec)
reconfig_task = vm.ReconfigVM_Task(changes_spec)
PyVmomiHelper.wait_task(
vm=vm,
answers={
"msg.cdromdisconnect.locked": '0'
},
task=reconfig_task
)
@staticmethod
def pre_install_operating_system(connection, vm_id):
pass
@staticmethod
def post_install_operating_system(connection, vm_id):
pass
@staticmethod
def mount_vendor_specific_resources(connection, virtual_host_context):
vm = PyVmomiHelper.find_vm_by_name(connection, virtual_host_context)
vm.MountToolsInstaller()
@staticmethod
def unmount_vendor_specific_resources(connection, virtual_host_context):
vm = PyVmomiHelper.find_vm_by_name(connection, virtual_host_context)
vm.UnmountToolsInstaller()
@staticmethod
def pre_install_vendor_specific_resources(hypervisor_config, operation, virtual_host):
pass
@staticmethod
def post_install_vendor_specific_resources(connection, vm_id):
pass
@staticmethod
def reboot(connection, vm_id):
pass
class Factory:
def create(self): return PyVmomiHypervisor()
if __name__ == '__main__':
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment