Source code for libcloud.compute.drivers.vsphere

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
VMware vSphere driver. Uses pyvmomi - https://github.com/vmware/pyvmomi
Code inspired by https://github.com/vmware/pyvmomi-community-samples

Authors: Dimitris Moraitis, Alex Tsiliris, Markos Gogoulos
"""

import ssl
import json
import time
import atexit
import base64
import asyncio
import hashlib
import logging
import warnings
import functools
import itertools

from libcloud.utils.py3 import httplib
from libcloud.common.base import JsonResponse, ConnectionKey
from libcloud.common.types import LibcloudError, ProviderError, InvalidCredsError
from libcloud.compute.base import Node, NodeSize, NodeImage, NodeDriver, NodeLocation
from libcloud.compute.types import Provider, NodeState
from libcloud.utils.networking import is_public_subnet
from libcloud.common.exceptions import BaseHTTPError

try:
    from pyVim import connect
    from pyVmomi import VmomiSupport, vim, vmodl
    from pyVim.task import WaitForTask

    pyvmomi = True
except ImportError:
    pyvmomi = None


logger = logging.getLogger("libcloud.compute.drivers.vsphere")


[docs]def recurse_snapshots(snapshot_list): ret = [] for s in snapshot_list: ret.append(s) ret += recurse_snapshots(getattr(s, "childSnapshotList", [])) return ret
[docs]def format_snapshots(snapshot_list): ret = [] for s in snapshot_list: ret.append( { "id": s.id, "name": s.name, "description": s.description, "created": s.createTime.strftime("%Y-%m-%d %H:%M"), "state": s.state, } ) return ret
# 6.5 and older, probably won't work on anything earlier than 4.x
[docs]class VSphereNodeDriver(NodeDriver): name = "VMware vSphere" website = "http://www.vmware.com/products/vsphere/" type = Provider.VSPHERE NODE_STATE_MAP = { "poweredOn": NodeState.RUNNING, "poweredOff": NodeState.STOPPED, "suspended": NodeState.SUSPENDED, } def __init__(self, host, username, password, port=443, ca_cert=None): """Initialize a connection by providing a hostname, username and password """ if pyvmomi is None: raise ImportError( 'Missing "pyvmomi" dependency. ' "You can install it " "using pip - pip install pyvmomi" ) self.host = host try: if ca_cert is None: self.connection = connect.SmartConnect( host=host, port=port, user=username, pwd=password, ) else: context = ssl.create_default_context(cafile=ca_cert) self.connection = connect.SmartConnect( host=host, port=port, user=username, pwd=password, sslContext=context, ) atexit.register(connect.Disconnect, self.connection) except Exception as exc: error_message = str(exc).lower() if "incorrect user name" in error_message: raise InvalidCredsError("Check your username and " "password are valid") if "connection refused" in error_message or "is not a vim server" in error_message: raise LibcloudError( "Check that the host provided is a " "vSphere installation", driver=self, ) if "name or service not known" in error_message: raise LibcloudError("Check that the vSphere host is accessible", driver=self) if "certificate verify failed" in error_message: # bypass self signed certificates try: context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.verify_mode = ssl.CERT_NONE except ImportError: raise ImportError( "To use self signed certificates, " "please upgrade to python 2.7.11 and " "pyvmomi 6.0.0+" ) self.connection = connect.SmartConnect( host=host, port=port, user=username, pwd=password, sslContext=context, ) atexit.register(connect.Disconnect, self.connection) else: raise LibcloudError("Cannot connect to vSphere", driver=self)
[docs] def list_locations(self, ex_show_hosts_in_drs=True): """ Lists locations """ content = self.connection.RetrieveContent() potential_locations = [ dc for dc in content.viewManager.CreateContainerView( content.rootFolder, [vim.ClusterComputeResource, vim.HostSystem], recursive=True, ).view ] # Add hosts and clusters with DRS enabled locations = [] hosts_all = [] clusters = [] for location in potential_locations: if isinstance(location, vim.HostSystem): hosts_all.append(location) elif isinstance(location, vim.ClusterComputeResource): if location.configuration.drsConfig.enabled: clusters.append(location) if ex_show_hosts_in_drs: hosts = hosts_all else: hosts_filter = [host for cluster in clusters for host in cluster.host] hosts = [host for host in hosts_all if host not in hosts_filter] for cluster in clusters: locations.append(self._to_location(cluster)) for host in hosts: locations.append(self._to_location(host)) return locations
def _to_location(self, data): try: if isinstance(data, vim.HostSystem): extra = { "type": "host", "state": data.runtime.connectionState, "hypervisor": data.config.product.fullName, "vendor": data.hardware.systemInfo.vendor, "model": data.hardware.systemInfo.model, "ram": data.hardware.memorySize, "cpu": { "packages": data.hardware.cpuInfo.numCpuPackages, "cores": data.hardware.cpuInfo.numCpuCores, "threads": data.hardware.cpuInfo.numCpuThreads, }, "uptime": data.summary.quickStats.uptime, "parent": str(data.parent), } elif isinstance(data, vim.ClusterComputeResource): extra = { "type": "cluster", "overallStatus": data.overallStatus, "drs": data.configuration.drsConfig.enabled, "hosts": [host.name for host in data.host], "parent": str(data.parent), } except AttributeError as exc: logger.error("Cannot convert location {}: {!r}".format(data.name, exc)) extra = {} return NodeLocation(id=data.name, name=data.name, country=None, extra=extra, driver=self)
[docs] def ex_list_networks(self): """ List networks """ content = self.connection.RetrieveContent() networks = content.viewManager.CreateContainerView( content.rootFolder, [vim.Network], recursive=True ).view return [self._to_network(network) for network in networks]
def _to_network(self, data): summary = data.summary extra = { "hosts": [h.name for h in data.host], "ip_pool_name": summary.ipPoolName, "ip_pool_id": summary.ipPoolId, "accessible": summary.accessible, } return VSphereNetwork(id=data.name, name=data.name, extra=extra)
[docs] def list_sizes(self): """ Returns sizes """ return []
[docs] def list_images(self, location=None, folder_ids=None): """ Lists VM templates as images. If folder is given then it will list images contained in that folder only. """ images = [] if folder_ids: vms = [] for folder_id in folder_ids: folder_object = self._get_item_by_moid("Folder", folder_id) vms.extend(folder_object.childEntity) else: content = self.connection.RetrieveContent() vms = content.viewManager.CreateContainerView( content.rootFolder, [vim.VirtualMachine], recursive=True ).view for vm in vms: if vm.config and vm.config.template: images.append(self._to_image(vm)) return images
def _to_image(self, data): summary = data.summary name = summary.config.name uuid = summary.config.instanceUuid memory = summary.config.memorySizeMB cpus = summary.config.numCpu operating_system = summary.config.guestFullName os_type = "unix" if "Microsoft" in str(operating_system): os_type = "windows" annotation = summary.config.annotation extra = { "path": summary.config.vmPathName, "operating_system": operating_system, "os_type": os_type, "memory_MB": memory, "cpus": cpus, "overallStatus": str(summary.overallStatus), "metadata": {}, "type": "template_6_5", "disk_size": int(summary.storage.committed) // (1024**3), "datastore": data.datastore[0].info.name, } boot_time = summary.runtime.bootTime if boot_time: extra["boot_time"] = boot_time.isoformat() if annotation: extra["annotation"] = annotation for custom_field in data.customValue: key_id = custom_field.key key = self.find_custom_field_key(key_id) extra["metadata"][key] = custom_field.value return NodeImage(id=uuid, name=name, driver=self, extra=extra) def _collect_properties(self, content, 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: content (ServiceInstance): ServiceInstance content 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 = content.propertyCollector # Create object specification to define the starting point of # inventory navigation obj_spec = 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 = 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 = 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 = 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
[docs] def list_nodes(self, enhance=True, max_properties=20): """ List nodes, excluding templates """ vm_properties = [ "config.template", "summary.config.name", "summary.config.vmPathName", "summary.config.memorySizeMB", "summary.config.numCpu", "summary.storage.committed", "summary.config.guestFullName", "summary.runtime.host", "summary.config.instanceUuid", "summary.config.annotation", "summary.runtime.powerState", "summary.runtime.bootTime", "summary.guest.ipAddress", "summary.overallStatus", "customValue", "snapshot", ] content = self.connection.RetrieveContent() view = content.viewManager.CreateContainerView( content.rootFolder, [vim.VirtualMachine], True ) i = 0 vm_dict = {} while i < len(vm_properties): vm_list = self._collect_properties( content, view, vim.VirtualMachine, path_set=vm_properties[i : i + max_properties], include_mors=True, ) i += max_properties for vm in vm_list: if not vm_dict.get(vm["obj"]): vm_dict[vm["obj"]] = vm else: vm_dict[vm["obj"]].update(vm) vm_list = [vm_dict[k] for k in vm_dict] loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) nodes = loop.run_until_complete(self._to_nodes(vm_list)) if enhance: nodes = self._enhance_metadata(nodes, content) return nodes
[docs] def list_nodes_recursive(self, enhance=True): """ Lists nodes, excluding templates """ nodes = [] content = self.connection.RetrieveContent() children = content.rootFolder.childEntity # this will be needed for custom VM metadata if content.customFieldsManager: self.custom_fields = content.customFieldsManager.field else: self.custom_fields = [] for child in children: if hasattr(child, "vmFolder"): datacenter = child vm_folder = datacenter.vmFolder vm_list = vm_folder.childEntity nodes.extend(self._to_nodes_recursive(vm_list)) if enhance: nodes = self._enhance_metadata(nodes, content) return nodes
def _enhance_metadata(self, nodes, content): nodemap = {} for node in nodes: node.extra["vSphere version"] = content.about.version nodemap[node.id] = node # Get VM deployment events to extract creation dates & images filter_spec = vim.event.EventFilterSpec(eventTypeId=["VmBeingDeployedEvent"]) deploy_events = content.eventManager.QueryEvent(filter_spec) for event in deploy_events: try: uuid = event.vm.vm.config.instanceUuid except Exception: continue if uuid in nodemap: node = nodemap[uuid] try: # Get source template as image source_template_vm = event.srcTemplate.vm image_id = source_template_vm.config.instanceUuid node.extra["image_id"] = image_id except Exception: logger.error("Cannot get instanceUuid " "from source template") try: # Get creation date node.created_at = event.createdTime except AttributeError: logger.error("Cannot get creation date from VM " "deploy event") return nodes async def _to_nodes(self, vm_list): vms = [] for vm in vm_list: if vm.get("config.template"): continue # Do not include templates in node list vms.append(vm) loop = asyncio.get_event_loop() vms = [loop.run_in_executor(None, self._to_node, vms[i]) for i in range(len(vms))] return await asyncio.gather(*vms) def _to_nodes_recursive(self, vm_list): nodes = [] for virtual_machine in vm_list: if hasattr(virtual_machine, "childEntity"): # If this is a group it will have children. # If it does, recurse into them and then return nodes.extend(self._to_nodes_recursive(virtual_machine.childEntity)) elif isinstance(virtual_machine, vim.VirtualApp): # If this is a vApp, it likely contains child VMs # (vApps can nest vApps, but it is hardly # a common usecase, so ignore that) nodes.extend(self._to_nodes_recursive(virtual_machine.vm)) else: if not hasattr(virtual_machine, "config") or ( virtual_machine.config and virtual_machine.config.template ): continue # Do not include templates in node list nodes.append(self._to_node_recursive(virtual_machine)) return nodes def _to_node(self, vm): name = vm.get("summary.config.name") path = vm.get("summary.config.vmPathName") memory = vm.get("summary.config.memorySizeMB") cpus = vm.get("summary.config.numCpu") disk = vm.get("summary.storage.committed", 0) // (1024**3) id_to_hash = str(memory) + str(cpus) + str(disk) size_id = hashlib.md5(id_to_hash.encode("utf-8")).hexdigest() size_name = name + "-size" size_extra = {"cpus": cpus} driver = self size = NodeSize( id=size_id, name=size_name, ram=memory, disk=disk, bandwidth=0, price=0, driver=driver, extra=size_extra, ) operating_system = vm.get("summary.config.guestFullName") host = vm.get("summary.runtime.host") os_type = "unix" if "Microsoft" in str(operating_system): os_type = "windows" uuid = vm.get("summary.config.instanceUuid") or ( vm.get("obj").config and vm.get("obj").config.instanceUuid ) if not uuid: logger.error("No uuid for vm: {}".format(vm)) annotation = vm.get("summary.config.annotation") state = vm.get("summary.runtime.powerState") status = self.NODE_STATE_MAP.get(state, NodeState.UNKNOWN) boot_time = vm.get("summary.runtime.bootTime") ip_addresses = [] if vm.get("summary.guest.ipAddress"): ip_addresses.append(vm.get("summary.guest.ipAddress")) overall_status = str(vm.get("summary.overallStatus")) public_ips = [] private_ips = [] extra = { "path": path, "operating_system": operating_system, "os_type": os_type, "memory_MB": memory, "cpus": cpus, "overall_status": overall_status, "metadata": {}, "snapshots": [], } if disk: extra["disk"] = disk if host: extra["host"] = host.name parent = host.parent while parent: if isinstance(parent, vim.ClusterComputeResource): extra["cluster"] = parent.name break parent = parent.parent if boot_time: extra["boot_time"] = boot_time.isoformat() if annotation: extra["annotation"] = annotation for ip_address in ip_addresses: try: if is_public_subnet(ip_address): public_ips.append(ip_address) else: private_ips.append(ip_address) except Exception: # IPV6 not supported pass if vm.get("snapshot"): extra["snapshots"] = format_snapshots( recurse_snapshots(vm.get("snapshot").rootSnapshotList) ) for custom_field in vm.get("customValue", []): key_id = custom_field.key key = self.find_custom_field_key(key_id) extra["metadata"][key] = custom_field.value node = Node( id=uuid, name=name, state=status, size=size, public_ips=public_ips, private_ips=private_ips, driver=self, extra=extra, ) node._uuid = uuid return node def _to_node_recursive(self, virtual_machine): summary = virtual_machine.summary name = summary.config.name path = summary.config.vmPathName memory = summary.config.memorySizeMB cpus = summary.config.numCpu disk = "" if summary.storage.committed: disk = summary.storage.committed / (1024**3) id_to_hash = str(memory) + str(cpus) + str(disk) size_id = hashlib.md5(id_to_hash.encode("utf-8")).hexdigest() size_name = name + "-size" size_extra = {"cpus": cpus} driver = self size = NodeSize( id=size_id, name=size_name, ram=memory, disk=disk, bandwidth=0, price=0, driver=driver, extra=size_extra, ) operating_system = summary.config.guestFullName host = summary.runtime.host # mist.io needs this metadata os_type = "unix" if "Microsoft" in str(operating_system): os_type = "windows" uuid = summary.config.instanceUuid annotation = summary.config.annotation state = summary.runtime.powerState status = self.NODE_STATE_MAP.get(state, NodeState.UNKNOWN) boot_time = summary.runtime.bootTime ip_addresses = [] if summary.guest is not None: ip_addresses.append(summary.guest.ipAddress) overall_status = str(summary.overallStatus) public_ips = [] private_ips = [] extra = { "path": path, "operating_system": operating_system, "os_type": os_type, "memory_MB": memory, "cpus": cpus, "overallStatus": overall_status, "metadata": {}, "snapshots": [], } if disk: extra["disk"] = disk if host: extra["host"] = host.name parent = host.parent while parent: if isinstance(parent, vim.ClusterComputeResource): extra["cluster"] = parent.name break parent = parent.parent if boot_time: extra["boot_time"] = boot_time.isoformat() if annotation: extra["annotation"] = annotation for ip_address in ip_addresses: try: if is_public_subnet(ip_address): public_ips.append(ip_address) else: private_ips.append(ip_address) except Exception: # IPV6 not supported pass if virtual_machine.snapshot: snapshots = [ { "id": s.id, "name": s.name, "description": s.description, "created": s.createTime.strftime("%Y-%m-%d %H:%M"), "state": s.state, } for s in virtual_machine.snapshot.rootSnapshotList ] extra["snapshots"] = snapshots for custom_field in virtual_machine.customValue: key_id = custom_field.key key = self.find_custom_field_key(key_id) extra["metadata"][key] = custom_field.value node = Node( id=uuid, name=name, state=status, size=size, public_ips=public_ips, private_ips=private_ips, driver=self, extra=extra, ) node._uuid = uuid return node
[docs] def reboot_node(self, node): """ """ vm = self.find_by_uuid(node.id) return self.wait_for_task(vm.RebootGuest())
[docs] def destroy_node(self, node): """ """ vm = self.find_by_uuid(node.id) if node.state != NodeState.STOPPED: self.stop_node(node) return self.wait_for_task(vm.Destroy())
[docs] def stop_node(self, node): """ """ vm = self.find_by_uuid(node.id) return self.wait_for_task(vm.PowerOff())
[docs] def start_node(self, node): """ """ vm = self.find_by_uuid(node.id) return self.wait_for_task(vm.PowerOn())
[docs] def ex_list_snapshots(self, node): """ List node snapshots """ vm = self.find_by_uuid(node.id) if not vm.snapshot: return [] return format_snapshots(recurse_snapshots(vm.snapshot.rootSnapshotList))
[docs] def ex_create_snapshot( self, node, snapshot_name, description="", dump_memory=False, quiesce=False ): """ Create node snapshot """ vm = self.find_by_uuid(node.id) return WaitForTask(vm.CreateSnapshot(snapshot_name, description, dump_memory, quiesce))
[docs] def ex_remove_snapshot(self, node, snapshot_name=None, remove_children=True): """ Remove a snapshot from node. If snapshot_name is not defined remove the last one. """ vm = self.find_by_uuid(node.id) if not vm.snapshot: raise LibcloudError( "Remove snapshot failed. No snapshots for node %s" % node.name, driver=self, ) snapshots = recurse_snapshots(vm.snapshot.rootSnapshotList) if not snapshot_name: snapshot = snapshots[-1].snapshot else: for s in snapshots: if snapshot_name == s.name: snapshot = s.snapshot break else: raise LibcloudError("Snapshot `%s` not found" % snapshot_name, driver=self) return self.wait_for_task(snapshot.RemoveSnapshot_Task(removeChildren=remove_children))
[docs] def ex_revert_to_snapshot(self, node, snapshot_name=None): """ Revert node to a specific snapshot. If snapshot_name is not defined revert to the last one. """ vm = self.find_by_uuid(node.id) if not vm.snapshot: raise LibcloudError( "Revert failed. No snapshots " "for node %s" % node.name, driver=self ) snapshots = recurse_snapshots(vm.snapshot.rootSnapshotList) if not snapshot_name: snapshot = snapshots[-1].snapshot else: for s in snapshots: if snapshot_name == s.name: snapshot = s.snapshot break else: raise LibcloudError("Snapshot `%s` not found" % snapshot_name, driver=self) return self.wait_for_task(snapshot.RevertToSnapshot_Task())
def _find_template_by_uuid(self, template_uuid): # on version 5.5 and earlier search index won't return a VM try: template = self.find_by_uuid(template_uuid) except LibcloudError: content = self.connection.RetrieveContent() vms = content.viewManager.CreateContainerView( content.rootFolder, [vim.VirtualMachine], recursive=True ).view for vm in vms: if vm.config.instanceUuid == template_uuid: template = vm except Exception as exc: raise LibcloudError("Error while searching for template: %s" % exc, driver=self) if not template: raise LibcloudError("Unable to locate VirtualMachine.", driver=self) return template
[docs] def find_by_uuid(self, node_uuid): """Searches VMs for a given uuid returns pyVmomi.VmomiSupport.vim.VirtualMachine """ vm = self.connection.content.searchIndex.FindByUuid(None, node_uuid, True, True) if not vm: # perhaps it is a moid vm = self._get_item_by_moid("VirtualMachine", node_uuid) if not vm: raise LibcloudError("Unable to locate VirtualMachine.", driver=self) return vm
[docs] def find_custom_field_key(self, key_id): """Return custom field key name, provided it's id""" if not hasattr(self, "custom_fields"): content = self.connection.RetrieveContent() if content.customFieldsManager: self.custom_fields = content.customFieldsManager.field else: self.custom_fields = [] for k in self.custom_fields: if k.key == key_id: return k.name return None
[docs] def get_obj(self, vimtype, name): """ Return an object by name, if name is None the first found object is returned """ obj = None content = self.connection.RetrieveContent() container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True) for c in container.view: if name: if c.name == name: obj = c break else: obj = c break return obj
[docs] def wait_for_task(self, task, timeout=1800, interval=10): """wait for a vCenter task to finish""" start_time = time.time() task_done = False while not task_done: if time.time() - start_time >= timeout: raise LibcloudError( "Timeout while waiting " "for import task Id %s" % task.info.id, driver=self, ) if task.info.state == "success": if task.info.result and str(task.info.result) != "success": return task.info.result return True if task.info.state == "error": raise LibcloudError(task.info.error.msg, driver=self) time.sleep(interval)
[docs] def create_node( self, name, image, size, location=None, ex_cluster=None, ex_network=None, ex_datacenter=None, ex_folder=None, ex_resource_pool=None, ex_datastore_cluster=None, ex_datastore=None, ): """ Creates and returns node. :keyword ex_network: Name of a "Network" to connect the VM to ", :type ex_network: ``str`` """ template = self._find_template_by_uuid(image.id) if ex_cluster: cluster_name = ex_cluster else: cluster_name = location.name cluster = self.get_obj([vim.ClusterComputeResource], cluster_name) if not cluster: # It is a host go with it cluster = self.get_obj([vim.HostSystem], cluster_name) datacenter = None if not ex_datacenter: # Get datacenter from cluster parent = cluster.parent while parent: if isinstance(parent, vim.Datacenter): datacenter = parent break parent = parent.parent if ex_datacenter or datacenter is None: datacenter = self.get_obj([vim.Datacenter], ex_datacenter) if ex_folder: folder = self.get_obj([vim.Folder], ex_folder) if folder is None: folder = self._get_item_by_moid("Folder", ex_folder) else: folder = datacenter.vmFolder if ex_resource_pool: resource_pool = self.get_obj([vim.ResourcePool], ex_resource_pool) else: try: resource_pool = cluster.resourcePool except AttributeError: resource_pool = cluster.parent.resourcePool devices = [] vmconf = vim.vm.ConfigSpec( numCPUs=int(size.extra.get("cpu", 1)), memoryMB=int(size.ram), deviceChange=devices, ) datastore = None pod = None podsel = vim.storageDrs.PodSelectionSpec() if ex_datastore_cluster: pod = self.get_obj([vim.StoragePod], ex_datastore_cluster) else: content = self.connection.RetrieveContent() pods = content.viewManager.CreateContainerView( content.rootFolder, [vim.StoragePod], True ).view for pod in pods: if cluster.name.lower() in pod.name: break podsel.storagePod = pod storagespec = vim.storageDrs.StoragePlacementSpec() storagespec.podSelectionSpec = podsel storagespec.type = "create" storagespec.folder = folder storagespec.resourcePool = resource_pool storagespec.configSpec = vmconf try: content = self.connection.RetrieveContent() rec = content.storageResourceManager.RecommendDatastores(storageSpec=storagespec) rec_action = rec.recommendations[0].action[0] real_datastore_name = rec_action.destination.name except Exception: real_datastore_name = template.datastore[0].info.name datastore = self.get_obj([vim.Datastore], real_datastore_name) if ex_datastore: datastore = self.get_obj([vim.Datastore], ex_datastore) if datastore is None: datastore = self._get_item_by_moid("Datastore", ex_datastore) elif not datastore: datastore = self.get_obj([vim.Datastore], template.datastore[0].info.name) add_network = True if ex_network and len(template.network) > 0: for nets in template.network: if template in nets.vm: add_network = False if ex_network and add_network: nicspec = vim.vm.device.VirtualDeviceSpec() nicspec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add nicspec.device = vim.vm.device.VirtualVmxnet3() nicspec.device.wakeOnLanEnabled = True nicspec.device.deviceInfo = vim.Description() portgroup = self.get_obj([vim.dvs.DistributedVirtualPortgroup], ex_network) if portgroup: dvs_port_connection = vim.dvs.PortConnection() dvs_port_connection.portgroupKey = portgroup.key dvs_port_connection.switchUuid = portgroup.config.distributedVirtualSwitch.uuid nicspec.device.backing = ( vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo() ) nicspec.device.backing.port = dvs_port_connection else: nicspec.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo() nicspec.device.backing.network = self.get_obj([vim.Network], ex_network) nicspec.device.backing.deviceName = ex_network nicspec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo() nicspec.device.connectable.startConnected = True nicspec.device.connectable.connected = True nicspec.device.connectable.allowGuestControl = True devices.append(nicspec) # new_disk_kb = int(size.disk) * 1024 * 1024 # disk_spec = vim.vm.device.VirtualDeviceSpec() # disk_spec.fileOperation = "create" # disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add # disk_spec.device = vim.vm.device.VirtualDisk() # disk_spec.device.backing = \ # vim.vm.device.VirtualDisk.FlatVer2BackingInfo() # if size.extra.get('disk_type') == 'thin': # disk_spec.device.backing.thinProvisioned = True # disk_spec.device.backing.diskMode = 'persistent' # disk_spec.device.capacityInKB = new_disk_kb # disk_spec.device.controllerKey = controller.key # devices.append(disk_spec) clonespec = vim.vm.CloneSpec(config=vmconf) relospec = vim.vm.RelocateSpec() relospec.datastore = datastore relospec.pool = resource_pool if location: host = self.get_obj([vim.HostSystem], location.name) if host: relospec.host = host clonespec.location = relospec clonespec.powerOn = True task = template.Clone(folder=folder, name=name, spec=clonespec) return self._to_node_recursive(self.wait_for_task(task))
[docs] def ex_connect_network(self, vm, network_name): spec = vim.vm.ConfigSpec() # add Switch here dev_changes = [] network_spec = vim.vm.device.VirtualDeviceSpec() # network_spec.fileOperation = "create" network_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add network_spec.device = vim.vm.device.VirtualVmxnet3() network_spec.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo() network_spec.device.backing.useAutoDetect = False network_spec.device.backing.network = self.get_obj([vim.Network], network_name) network_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo() network_spec.device.connectable.startConnected = True network_spec.device.connectable.connected = True network_spec.device.connectable.allowGuestControl = True dev_changes.append(network_spec) spec.deviceChange = dev_changes output = vm.ReconfigVM_Task(spec=spec) print(output.info)
def _get_item_by_moid(self, type_, moid): vm_obj = VmomiSupport.templateOf(type_)(moid, self.connection._stub) return vm_obj
[docs] def ex_list_folders(self): content = self.connection.RetrieveContent() folders_raw = content.viewManager.CreateContainerView( content.rootFolder, [vim.Folder], True ).view folders = [] for folder in folders_raw: to_add = {"type": list(folder.childType)} to_add["name"] = folder.name to_add["id"] = folder._moId folders.append(to_add) return folders
[docs] def ex_list_datastores(self): content = self.connection.RetrieveContent() datastores_raw = content.viewManager.CreateContainerView( content.rootFolder, [vim.Datastore], True ).view datastores = [] for dstore in datastores_raw: to_add = {"type": dstore.summary.type} to_add["name"] = dstore.name to_add["id"] = dstore._moId to_add["free_space"] = int(dstore.summary.freeSpace) to_add["capacity"] = int(dstore.summary.capacity) datastores.append(to_add) return datastores
[docs] def ex_open_console(self, vm_uuid): vm = self.find_by_uuid(vm_uuid) ticket = vm.AcquireTicket(ticketType="webmks") return "wss://{}:{}/ticket/{}".format(ticket.host, ticket.port, ticket.ticket)
def _get_version(self): content = self.connection.RetrieveContent() return content.about.version
[docs]class VSphereNetwork: """ Represents information about a VPC (Virtual Private Cloud) network Note: This class is VSphere specific. """ def __init__(self, id, name, extra=None): self.id = id self.name = name self.extra = extra or {} def __repr__(self): return ("<VSphereNetwork: id=%s, name=%s") % (self.id, self.name)
# 6.7
[docs]class VSphereResponse(JsonResponse):
[docs] def parse_error(self): if self.body: message = self.body message += "-- code: {}".format(self.status) return message return self.body
[docs]class VSphereConnection(ConnectionKey): responseCls = VSphereResponse session_token = None
[docs] def add_default_headers(self, headers): """ VSphere needs an initial connection to a specific API endpoint to generate a session-token, which will be used for the purpose of authenticating for the rest of the session. """ headers["Content-Type"] = "application/json" headers["Accept"] = "application/json" if self.session_token is None: to_encode = "{}:{}".format(self.key, self.secret) b64_user_pass = base64.b64encode(to_encode.encode()) headers["Authorization"] = "Basic {}".format(b64_user_pass.decode()) else: headers["vmware-api-session-id"] = self.session_token return headers
[docs]class VSphereException(Exception): def __init__(self, code, message): self.code = code self.message = message self.args = (code, message) def __str__(self): return "{} {}".format(self.code, self.message) def __repr__(self): return "VSphereException {} {}".format(self.code, self.message)
[docs]class VSphere_REST_NodeDriver(NodeDriver): name = "VMware vSphere" website = "http://www.vmware.com/products/vsphere/" type = Provider.VSPHERE connectionCls = VSphereConnection session_token = None NODE_STATE_MAP = { "powered_on": NodeState.RUNNING, "powered_off": NodeState.STOPPED, "suspended": NodeState.SUSPENDED, } VALID_RESPONSE_CODES = [ httplib.OK, httplib.ACCEPTED, httplib.CREATED, httplib.NO_CONTENT, ] def __init__(self, key, secret=None, secure=True, host=None, port=443, ca_cert=None): if not key or not secret: raise InvalidCredsError("Please provide both username " "(key) and password (secret).") super().__init__(key=key, secure=secure, host=host, port=port) prefixes = ["http://", "https://"] for prefix in prefixes: if host.startswith(prefix): host = host.lstrip(prefix) if ca_cert: self.connection.connection.ca_cert = ca_cert else: self.connection.connection.ca_cert = False self.connection.secret = secret self.host = host self.username = key # getting session token self._get_session_token() self.driver_soap = None def _get_soap_driver(self): if pyvmomi is None: raise ImportError( 'Missing "pyvmomi" dependency. ' "You can install it " "using pip - pip install pyvmomi" ) self.driver_soap = VSphereNodeDriver( self.host, self.username, self.connection.secret, ca_cert=self.connection.connection.ca_cert, ) def _get_session_token(self): uri = "/rest/com/vmware/cis/session" try: result = self.connection.request(uri, method="POST") except Exception: raise self.session_token = result.object["value"] self.connection.session_token = self.session_token
[docs] def list_sizes(self): return []
[docs] def list_nodes( self, ex_filter_power_states=None, ex_filter_folders=None, ex_filter_names=None, ex_filter_hosts=None, ex_filter_clusters=None, ex_filter_vms=None, ex_filter_datacenters=None, ex_filter_resource_pools=None, max_properties=20, ): """ The ex parameters are search options and must be an array of strings, even ex_filter_power_states which can have at most two items but makes sense to keep only one ("POWERED_ON" or "POWERED_OFF") Keep in mind that this method will return up to 1000 nodes so if your network has more, do use the provided filters and call it multiple times. """ req = "/rest/vcenter/vm" kwargs = { "filter.power_states": ex_filter_power_states, "filter.folders": ex_filter_folders, "filter.names": ex_filter_names, "filter.hosts": ex_filter_hosts, "filter.clusters": ex_filter_clusters, "filter.vms": ex_filter_vms, "filter.datacenters": ex_filter_datacenters, "filter.resource_pools": ex_filter_resource_pools, } params = {} for param, value in kwargs.items(): if value: params[param] = value result = self._request(req, params=params).object["value"] vm_ids = [[item["vm"]] for item in result] vms = [] interfaces = self._list_interfaces() for vm_id in vm_ids: vms.append(self._to_node(vm_id, interfaces)) return vms
[docs] def async_list_nodes(self): """ In this case filtering is not possible. Use this method when the cloud has a lot of vms and you want to return them all. """ loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) result = loop.run_until_complete(self._get_all_vms()) vm_ids = [(item["vm"], item["host"]) for item in result] interfaces = self._list_interfaces() return loop.run_until_complete(self._list_nodes_async(vm_ids, interfaces))
async def _list_nodes_async(self, vm_ids, interfaces): loop = asyncio.get_event_loop() vms = [ loop.run_in_executor(None, self._to_node, vm_ids[i], interfaces) for i in range(len(vm_ids)) ] return await asyncio.gather(*vms) async def _get_all_vms(self): """ 6.7 doesn't offer any pagination, if we get 1000 vms we will try this roundabout way: First get all the datacenters, for each datacenter get the hosts and for each host the vms it has. This assumes that datacenters, hosts per datacenter and vms per host don't exceed 1000. """ datacenters = self.ex_list_datacenters() loop = asyncio.get_event_loop() hosts_futures = [ loop.run_in_executor( None, functools.partial(self.ex_list_hosts, ex_filter_datacenters=datacenter["id"]), ) for datacenter in datacenters ] hosts = await asyncio.gather(*hosts_futures) vm_resp_futures = [ loop.run_in_executor(None, functools.partial(self._get_vms_with_host, host)) for host in itertools.chain(*hosts) ] vm_resp = await asyncio.gather(*vm_resp_futures) # return a flat list return [item for vm_list in vm_resp for item in vm_list] def _get_vms_with_host(self, host): req = "/rest/vcenter/vm" host_id = host["host"] response = self._request(req, params={"filter.hosts": host_id}) vms = response.object["value"] for vm in vms: vm["host"] = host return vms
[docs] def list_locations(self, ex_show_hosts_in_drs=True): """ Location in the general sense means any resource that allows for node creation. In vSphere's case that usually is a host but if a cluster has rds enabled, a cluster can be assigned to create the VM, thus the clusters with rds enabled will be added to locations. :param ex_show_hosts_in_drs: A DRS cluster schedules automatically on which host should be placed thus it may not be desired to show the hosts in a DRS enabled cluster. Set False to not show these hosts. :type ex_show_hosts_in_drs: `boolean` """ clusters = self.ex_list_clusters() hosts_all = self.ex_list_hosts() hosts = [] if ex_show_hosts_in_drs: hosts = hosts_all else: cluster_filter = [cluster["cluster"] for cluster in clusters] filter_hosts = self.ex_list_hosts(ex_filter_clusters=cluster_filter) hosts = [host for host in hosts_all if host not in filter_hosts] driver = self.connection.driver locations = [] for cluster in clusters: if cluster["drs_enabled"]: extra = {"type": "cluster", "drs": True, "ha": cluster["ha_enabled"]} locations.append( NodeLocation( id=cluster["cluster"], name=cluster["name"], country="", driver=driver, extra=extra, ) ) for host in hosts: extra = { "type": "host", "status": host["connection_state"], "state": host["power_state"], } locations.append( NodeLocation( id=host["host"], name=host["name"], country="", driver=driver, extra=extra, ) ) return locations
[docs] def stop_node(self, node): if node.state == NodeState.STOPPED: return True method = "POST" req = "/rest/vcenter/vm/{}/power/stop".format(node.id) result = self._request(req, method=method) return result.status in self.VALID_RESPONSE_CODES
[docs] def start_node(self, node): if isinstance(node, str): node_id = node else: if node.state is NodeState.RUNNING: return True node_id = node.id method = "POST" req = "/rest/vcenter/vm/{}/power/start".format(node_id) result = self._request(req, method=method) return result.status in self.VALID_RESPONSE_CODES
[docs] def reboot_node(self, node): if node.state is not NodeState.RUNNING: return False method = "POST" req = "/rest/vcenter/vm/{}/power/reset".format(node.id) result = self._request(req, method=method) return result.status in self.VALID_RESPONSE_CODES
[docs] def destroy_node(self, node): # make sure the machine is stopped if node.state is not NodeState.STOPPED: self.stop_node(node) # wait to make sure it stopped # in the future this can be made asynchronously # for i in range(6): # if node.state is NodeState.STOPPED: # break # time.sleep(10) req = "/rest/vcenter/vm/{}".format(node.id) resp = self._request(req, method="DELETE") return resp.status in self.VALID_RESPONSE_CODES
[docs] def ex_suspend_node(self, node): if node.state is not NodeState.RUNNING: return False method = "POST" req = "/rest/vcenter/vm/{}/power/suspend".format(node.id) result = self._request(req, method=method) return result.status in self.VALID_RESPONSE_CODES
def _list_interfaces(self): request = "/rest/appliance/networking/interfaces" response = self._request(request).object["value"] interfaces = [ { "name": interface["name"], "mac": interface["mac"], "status": interface["status"], "ip": interface["ipv4"]["address"], } for interface in response ] return interfaces def _to_node(self, vm_id_host, interfaces): """ id, name, state, public_ips, private_ips, driver, size=None, image=None, extra=None, created_at=None) """ vm_id = vm_id_host[0] req = "/rest/vcenter/vm/" + vm_id vm = self._request(req).object["value"] name = vm["name"] state = self.NODE_STATE_MAP[vm["power_state"].lower()] # IP's private_ips = [] nic_macs = set() for nic in vm["nics"]: nic_macs.add(nic["value"]["mac_address"]) for interface in interfaces: if interface["mac"] in nic_macs: private_ips.append(interface["ip"]) nic_macs.remove(interface["mac"]) if len(nic_macs) == 0: break public_ips = [] # should default_getaway be the public? driver = self.connection.driver # size total_size = 0 gb_converter = 1024**3 for disk in vm["disks"]: total_size += int(int(disk["value"]["capacity"] / gb_converter)) ram = int(vm["memory"]["size_MiB"]) cpus = int(vm["cpu"]["count"]) id_to_hash = str(ram) + str(cpus) + str(total_size) size_id = hashlib.md5(id_to_hash.encode("utf-8")).hexdigest() size_name = name + "-size" size_extra = {"cpus": cpus} size = NodeSize( id=size_id, name=size_name, ram=ram, disk=total_size, bandwidth=0, price=0, driver=driver, extra=size_extra, ) # image image_name = vm["guest_OS"] image_id = image_name + "_id" image_extra = {"type": "guest_OS"} image = NodeImage(id=image_id, name=image_name, driver=driver, extra=image_extra) extra = {"snapshots": []} if len(vm_id_host) > 1: extra["host"] = vm_id_host[1].get("name", "") return Node( id=vm_id, name=name, state=state, public_ips=public_ips, private_ips=private_ips, driver=driver, size=size, image=image, extra=extra, )
[docs] def ex_list_hosts( self, ex_filter_folders=None, ex_filter_standalone=None, ex_filter_hosts=None, ex_filter_clusters=None, ex_filter_names=None, ex_filter_datacenters=None, ex_filter_connection_states=None, ): kwargs = { "filter.folders": ex_filter_folders, "filter.names": ex_filter_names, "filter.hosts": ex_filter_hosts, "filter.clusters": ex_filter_clusters, "filter.standalone": ex_filter_standalone, "filter.datacenters": ex_filter_datacenters, "filter.connection_states": ex_filter_connection_states, } params = {} for param, value in kwargs.items(): if value: params[param] = value req = "/rest/vcenter/host" result = self._request(req, params=params).object["value"] return result
[docs] def ex_list_clusters( self, ex_filter_folders=None, ex_filter_names=None, ex_filter_datacenters=None, ex_filter_clusters=None, ): kwargs = { "filter.folders": ex_filter_folders, "filter.names": ex_filter_names, "filter.datacenters": ex_filter_datacenters, "filter.clusters": ex_filter_clusters, } params = {} for param, value in kwargs.items(): if value: params[param] = value req = "/rest/vcenter/cluster" result = self._request(req, params=params).object["value"] return result
[docs] def ex_list_datacenters( self, ex_filter_folders=None, ex_filter_names=None, ex_filter_datacenters=None ): req = "/rest/vcenter/datacenter" kwargs = { "filter.folders": ex_filter_folders, "filter.names": ex_filter_names, "filter.datacenters": ex_filter_datacenters, } params = {} for param, value in kwargs.items(): if value: params[param] = value result = self._request(req, params=params) to_return = [ {"name": item["name"], "id": item["datacenter"]} for item in result.object["value"] ] return to_return
[docs] def ex_list_content_libraries(self): req = "/rest/com/vmware/content/library" try: result = self._request(req).object return result["value"] except BaseHTTPError: return []
[docs] def ex_list_content_library_items(self, library_id): req = "/rest/com/vmware/content/library/item" params = {"library_id": library_id} try: result = self._request(req, params=params).object return result["value"] except BaseHTTPError: logger.error( "Library was cannot be accessed, " " most probably the VCenter service " "is stopped" ) return []
[docs] def ex_list_folders(self): req = "/rest/vcenter/folder" response = self._request(req).object folders = response["value"] for folder in folders: folder["id"] = folder["folder"] return folders
[docs] def ex_list_datastores( self, ex_filter_folders=None, ex_filter_names=None, ex_filter_datacenters=None, ex_filter_types=None, ex_filter_datastores=None, ): req = "/rest/vcenter/datastore" kwargs = { "filter.folders": ex_filter_folders, "filter.names": ex_filter_names, "filter.datacenters": ex_filter_datacenters, "filter.types": ex_filter_types, "filter.datastores": ex_filter_datastores, } params = {} for param, value in kwargs.items(): if value: params[param] = value result = self._request(req, params=params).object["value"] for datastore in result: datastore["id"] = datastore["datastore"] return result
[docs] def ex_update_memory(self, node, ram): """ :param ram: The amount of ram in MB. :type ram: `str` or `int` """ if isinstance(node, str): node_id = node else: node_id = node.id request = "/rest/vcenter/vm/{}/hardware/memory".format(node_id) ram = int(ram) body = {"spec": {"size_MiB": ram}} response = self._request(request, method="PATCH", data=json.dumps(body)) return response.status in self.VALID_RESPONSE_CODES
[docs] def ex_update_cpu(self, node, cores): """ Assuming 1 Core per socket :param cores: Integer or string indicating number of cores :type cores: `int` or `str` """ if isinstance(node, str): node_id = node else: node_id = node.id request = "/rest/vcenter/vm/{}/hardware/cpu".format(node_id) cores = int(cores) body = {"spec": {"count": cores}} response = self._request(request, method="PATCH", data=json.dumps(body)) return response.status in self.VALID_RESPONSE_CODES
[docs] def ex_update_capacity(self, node, capacity): # Should be added when REST API supports it pass
[docs] def ex_add_nic(self, node, network): """ Creates a network adapter that will connect to the specified network for the given node. Returns a boolean indicating success or not. """ if isinstance(node, str): node_id = node else: node_id = node.id spec = {} spec["mac_type"] = "GENERATED" spec["backing"] = {} spec["backing"]["type"] = "STANDARD_PORTGROUP" spec["backing"]["network"] = network spec["start_connected"] = True data = json.dumps({"spec": spec}) req = "/rest/vcenter/vm/{}/hardware/ethernet".format(node_id) method = "POST" resp = self._request(req, method=method, data=data) return resp.status
def _get_library_item(self, item_id): req = "/rest/com/vmware/content/library/item/id:{}".format(item_id) result = self._request(req).object return result["value"] def _get_resource_pool(self, host_id=None, cluster_id=None, name=None): if host_id: pms = {"filter.hosts": host_id} if cluster_id: pms = {"filter.clusters": cluster_id} if name: pms = {"filter.names": name} rp_request = "/rest/vcenter/resource-pool" resource_pool = self._request(rp_request, params=pms).object return resource_pool["value"][0]["resource_pool"] def _request(self, req, method="GET", params=None, data=None): try: result = self.connection.request(req, method=method, params=params, data=data) except BaseHTTPError as exc: if exc.code == 401: self.connection.session_token = None self._get_session_token() result = self.connection.request(req, method=method, params=params, data=data) else: raise except Exception: raise return result
[docs] def list_images(self, **kwargs): libraries = self.ex_list_content_libraries() item_ids = [] if libraries: for library in libraries: item_ids.extend(self.ex_list_content_library_items(library)) items = [] if item_ids: for item_id in item_ids: items.append(self._get_library_item(item_id)) images = [] names = set() if items: driver = self.connection.driver for item in items: names.add(item["name"]) extra = {"type": item["type"]} if item["type"] == "vm-template": capacity = item["size"] // (1024**3) extra["disk_size"] = capacity images.append( NodeImage(id=item["id"], name=item["name"], driver=driver, extra=extra) ) if self.driver_soap is None: self._get_soap_driver() templates_in_hosts = self.driver_soap.list_images() for template in templates_in_hosts: if template.name not in names: images += [template] return images
[docs] def ex_list_networks(self): request = "/rest/vcenter/network" response = self._request(request).object["value"] networks = [] for network in response: networks.append( VSphereNetwork( id=network["network"], name=network["name"], extra={"type": network["type"]}, ) ) return networks
[docs] def create_node( self, name, image, size=None, location=None, ex_datastore=None, ex_disks=None, ex_folder=None, ex_network=None, ex_turned_on=True, ): """ Image can be either a vm template , a ovf template or just the guest OS. ex_folder is necessary if the image is a vm-template, this method will attempt to put the VM in a random folder and a warning about it will be issued in case the value remains `None`. """ # image is in the host then need the 6.5 driver if image.extra["type"] == "template_6_5": kwargs = {} kwargs["name"] = name kwargs["image"] = image kwargs["size"] = size kwargs["ex_network"] = ex_network kwargs["location"] = location for dstore in self.ex_list_datastores(): if dstore["id"] == ex_datastore: kwargs["ex_datastore"] = dstore["name"] break kwargs["folder"] = ex_folder if self.driver_soap is None: self._get_soap_driver() result = self.driver_soap.create_node(**kwargs) return result # post creation checks create_nic = False update_memory = False update_cpu = False create_disk = False update_capacity = False if image.extra["type"] == "guest_OS": spec = {} spec["guest_OS"] = image.name spec["name"] = name spec["placement"] = {} if ex_folder is None: warn = ( "The API(6.7) requires the folder to be given, I will" " place it into a random folder, after creation you " "might find it convenient to move it into a better " "folder." ) warnings.warn(warn) folders = self.ex_list_folders() for folder in folders: if folder["type"] == "VIRTUAL_MACHINE": ex_folder = folder["folder"] if ex_folder is None: msg = "No suitable folder vor VMs found, please create one" raise ProviderError(msg, 404) spec["placement"]["folder"] = ex_folder if location.extra["type"] == "host": spec["placement"]["host"] = location.id elif location.extra["type"] == "cluster": spec["placement"]["cluster"] = location.id elif location.extra["type"] == "resource_pool": spec["placement"]["resource_pool"] = location.id spec["placement"]["datastore"] = ex_datastore cpu = size.extra.get("cpu", 1) spec["cpu"] = {"count": cpu} spec["memory"] = {"size_MiB": size.ram} if size.disk: disk = {} disk["new_vmdk"] = {} disk["new_vmdk"]["capacity"] = size.disk * (1024**3) spec["disks"] = [disk] if ex_network: nic = {} nic["mac_type"] = "GENERATED" nic["backing"] = {} nic["backing"]["type"] = "STANDARD_PORTGROUP" nic["backing"]["network"] = ex_network nic["start_connected"] = True spec["nics"] = [nic] create_request = "/rest/vcenter/vm" data = json.dumps({"spec": spec}) elif image.extra["type"] == "ovf": ovf_request = ( "/rest/com/vmware/vcenter/ovf/library-item" "/id:{}?~action=filter".format(image.id) ) spec = {} spec["target"] = {} if location.extra.get("type") == "resource-pool": spec["target"]["resource_pool_id"] = location.id elif location.extra.get("type") == "host": resource_pool = self._get_resource_pool(host_id=location.id) if not resource_pool: msg = ( "Could not find resource-pool for given location " "(host). Please make sure the location is valid." ) raise VSphereException(code="504", message=msg) spec["target"]["resource_pool_id"] = resource_pool spec["target"]["host_id"] = location.id elif location.extra.get("type") == "cluster": resource_pool = self._get_resource_pool(cluster_id=location.id) if not resource_pool: msg = ( "Could not find resource-pool for given location " "(cluster). Please make sure the location " "is valid." ) raise VSphereException(code="504", message=msg) spec["target"]["resource_pool_id"] = resource_pool ovf = self._request(ovf_request, method="POST", data=json.dumps(spec)).object["value"] spec["deployment_spec"] = {} spec["deployment_spec"]["name"] = name # assuming that since you want to make a vm you don't need reminder spec["deployment_spec"]["accept_all_EULA"] = True # network if ex_network and ovf["networks"]: spec["deployment_spec"]["network_mappings"] = [ {"key": ovf["networks"][0], "value": ex_network} ] elif not ovf["networks"] and ex_network: create_nic = True # storage if ex_datastore: spec["deployment_spec"]["storage_mappings"] = [] store_map = {"type": "DATASTORE", "datastore_id": ex_datastore} spec["deployment_spec"]["storage_mappings"].append(store_map) if size and size.ram: update_memory = True if size and size.extra and size.extra.get("cpu"): update_cpu = True if size and size.disk: # TODO Should update capacity but it is not possible with 6.7 pass if ex_disks: create_disk = True create_request = ( "/rest/com/vmware/vcenter/ovf/library-item" "/id:{}?~action=deploy".format(image.id) ) data = json.dumps( {"target": spec["target"], "deployment_spec": spec["deployment_spec"]} ) elif image.extra["type"] == "vm-template": tp_request = "/rest/vcenter/vm-template/library-items/" + image.id template = self._request(tp_request).object["value"] spec = {} spec["name"] = name # storage if ex_datastore: spec["disk_storage"] = {} spec["disk_storage"]["datastore"] = ex_datastore # location :: folder,resource group, datacenter, host spec["placement"] = {} if not ex_folder: warn = ( "The API(6.7) requires the folder to be given, I will" " place it into a random folder, after creation you " "might find it convenient to move it into a better " "folder." ) warnings.warn(warn) folders = self.ex_list_folders() for folder in folders: if folder["type"] == "VIRTUAL_MACHINE": ex_folder = folder["folder"] if ex_folder is None: msg = "No suitable folder vor VMs found, please create one" raise ProviderError(msg, 404) spec["placement"]["folder"] = ex_folder if location.extra["type"] == "host": spec["placement"]["host"] = location.id elif location.extra["type"] == "cluster": spec["placement"]["cluster"] = location.id # network changes the network to existing nics if # there are no adapters # in the template then we will make on in the vm # after the creation finishes # only one network atm?? spec["hardware_customization"] = {} if ex_network: nics = template["nics"] if len(nics) > 0: nic = nics[0] spec["hardware_customization"]["nics"] = [ {"key": nic["key"], "value": {"network": ex_network}} ] else: create_nic = True spec["powered_on"] = False # hardware if size: if size.ram: spec["hardware_customization"]["memory_update"] = {"memory": int(size.ram)} if size.extra.get("cpu"): spec["hardware_customization"]["cpu_update"] = {"num_cpus": size.extra["cpu"]} if size.disk: if not len(template["disks"]) > 0: create_disk = True else: capacity = size.disk * 1024 * 1024 * 1024 dsk = template["disks"][0]["key"] if template["disks"][0]["value"]["capacity"] < capacity: update = {"capacity": capacity} spec["hardware_customization"]["disks_to_update"] = [ {"key": dsk, "value": update} ] create_request = "/rest/vcenter/vm-template/library-items/" "{}/?action=deploy".format( image.id ) data = json.dumps({"spec": spec}) # deploy the node result = self._request(create_request, method="POST", data=data) # wait until the node is up and then add extra config node_id = result.object["value"] if image.extra["type"] == "ovf": node_id = node_id["resource_id"]["id"] node = self.list_nodes(ex_filter_vms=node_id)[0] if create_nic: self.ex_add_nic(node, ex_network) if update_memory: self.ex_update_memory(node, size.ram) if update_cpu: self.ex_update_cpu(node, size.extra["cpu"]) if create_disk: pass # until volumes are added if update_capacity: pass # until API method is added if ex_turned_on: self.start_node(node) return node
# TODO As soon as snapshot support gets added to the REST api # these methods should be rewritten with REST api calls
[docs] def ex_list_snapshots(self, node): """ List node snapshots """ if self.driver_soap is None: self._get_soap_driver() return self.driver_soap.ex_list_snapshots(node)
[docs] def ex_create_snapshot( self, node, snapshot_name, description="", dump_memory=False, quiesce=False ): """ Create node snapshot """ if self.driver_soap is None: self._get_soap_driver() return self.driver_soap.ex_create_snapshot( node, snapshot_name, description=description, dump_memory=dump_memory, quiesce=False, )
[docs] def ex_remove_snapshot(self, node, snapshot_name=None, remove_children=True): """ Remove a snapshot from node. If snapshot_name is not defined remove the last one. """ if self.driver_soap is None: self._get_soap_driver() return self.driver_soap.ex_remove_snapshot( node, snapshot_name=snapshot_name, remove_children=remove_children )
[docs] def ex_revert_to_snapshot(self, node, snapshot_name=None): """ Revert node to a specific snapshot. If snapshot_name is not defined revert to the last one. """ if self.driver_soap is None: self._get_soap_driver() return self.driver_soap.ex_revert_to_snapshot(node, snapshot_name=snapshot_name)
[docs] def ex_open_console(self, vm_id): if self.driver_soap is None: self._get_soap_driver() return self.driver_soap.ex_open_console(vm_id)