From 61b45e8865e0aba2bf5404c063d8807b4af7811e Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Fri, 23 Mar 2018 17:52:55 +0100 Subject: [PATCH] Created virtual machine --- etc/create-vmware-template.ini.default | 11 ++ lib/cr_vmware_tpl/config.py | 34 ++++- lib/cr_vmware_tpl/handler.py | 181 +++++++++++++++++++++++-- 3 files changed, 210 insertions(+), 16 deletions(-) diff --git a/etc/create-vmware-template.ini.default b/etc/create-vmware-template.ini.default index a58ab1e..ad9c3b2 100644 --- a/etc/create-vmware-template.ini.default +++ b/etc/create-vmware-template.ini.default @@ -20,6 +20,17 @@ ;data_size_gb = 30 +;num_cpus = 2 + +;ram_mb = 4096 + ;network = 192.168.88.0_23 +;mac_address = 00:50:56:01:af:fe + + + + + + ; vim: filetype=dosini diff --git a/lib/cr_vmware_tpl/config.py b/lib/cr_vmware_tpl/config.py index 5a661a0..784281d 100644 --- a/lib/cr_vmware_tpl/config.py +++ b/lib/cr_vmware_tpl/config.py @@ -26,7 +26,7 @@ from .errors import FunctionNotImplementedError, PpError from .obj import PpBaseObject -__version__ = '0.4.2' +__version__ = '0.4.3' LOG = logging.getLogger(__name__) @@ -53,7 +53,10 @@ class CrTplConfiguration(PpBaseObject): default_template_vm = 'template.pixelpark.com' default_template_name = 'oracle-linux-7.4-template' default_data_size_gb = 30.0 + default_num_cpus = 2 + default_ram_mb = 4 * 1024 default_network = '192.168.88.0_23' + default_mac_address = '00:50:56:01:af:fe' # ------------------------------------------------------------------------- def __init__( @@ -69,7 +72,10 @@ class CrTplConfiguration(PpBaseObject): self.template_vm = self.default_template_vm self.template_name = self.default_template_name self.data_size_gb = self.default_data_size_gb + self.num_cpus = self.default_num_cpus + self.ram_mb = self.default_ram_mb self.network = self.default_network + self.mac_address = self.default_mac_address self.encoding = 'utf-8' @@ -95,19 +101,25 @@ class CrTplConfiguration(PpBaseObject): @property def data_size_mb(self): """Size of template volume in MiB.""" - return int(self.data_size_gb) * 1024 + return int(self.data_size_gb * 1024.0) # ------------------------------------------------------------------------- @property def data_size_kb(self): """Size of template volume in KiB.""" - return int(self.data_size_gb) * 1024 * 1024 + return self.data_size_mb * 1024 # ------------------------------------------------------------------------- @property def data_size(self): """Size of template volume in Bytes.""" - return int(self.data_size_gb) * 1024 * 1024 * 1024 + return self.data_size_mb * 1024 * 1024 + + # ------------------------------------------------------------------------- + @property + def ram_gb(self): + """Size of RAM in GiB.""" + return float(self.ram_mb) / 1024 # ------------------------------------------------------------------------- def as_dict(self, short=True): @@ -130,10 +142,14 @@ class CrTplConfiguration(PpBaseObject): res['default_template_vm'] = self.default_template_vm res['default_template_name'] = self.default_template_name res['default_data_size_gb'] = self.default_data_size_gb + res['default_num_cpus'] = self.default_num_cpus + res['default_ram_mb'] = self.default_ram_mb res['default_network'] = self.default_network + res['default_mac_address'] = self.default_mac_address res['data_size_mb'] = self.data_size_mb res['data_size_kb'] = self.data_size_kb res['data_size'] = self.data_size + res['ram_gb'] = self.ram_gb return res @@ -238,8 +254,18 @@ class CrTplConfiguration(PpBaseObject): self.data_size_gb = float(value) / 1024.0 / 1024.0 if key.lower() == 'data_size': self.data_size_gb = float(value) / 1024.0 / 1024.0 / 1024.0 + if key.lower() == 'num_cpus': + self.num_cpus = int(value) + if key.lower() == 'ram_gb': + self.ram_mb = int(float(value) * 1024.0) + if key.lower() == 'ram_mb': + self.ram_mb = int(value) if key.lower() == 'network': self.network = value.strip() + if key.lower() == 'mac_address': + v = value.strip().lower() + if v: + self.mac_address = v # ============================================================================= diff --git a/lib/cr_vmware_tpl/handler.py b/lib/cr_vmware_tpl/handler.py index 13798ea..946f746 100644 --- a/lib/cr_vmware_tpl/handler.py +++ b/lib/cr_vmware_tpl/handler.py @@ -15,22 +15,25 @@ import logging import ssl import re import random +import time # Third party modules import six -from pyVmomi import vim +from pyVmomi import vim, vmodl from pyVim.connect import SmartConnect, Disconnect # Own modules +from .common import pp + from .errors import FunctionNotImplementedError, PpError from .obj import PpBaseObject from .config import CrTplConfiguration -__version__ = '0.4.1' +__version__ = '0.4.2' LOG = logging.getLogger(__name__) @@ -147,7 +150,7 @@ class CrTplHandler(PpBaseObject): ) self.config = config - self.server_instance = None + self.service_instance = None self.tpl_vm_folder = None self.tpl_data_store = None self.tpl_network = None @@ -176,12 +179,12 @@ class CrTplHandler(PpBaseObject): if hasattr(ssl, '_create_unverified_context'): ssl_context = ssl._create_unverified_context() - self.server_instance = SmartConnect( + self.service_instance = SmartConnect( host=self.config.vsphere_host, port=self.config.vsphere_port, user=self.config.vsphere_user, pwd=self.config.password, sslContext=ssl_context) - if not self.server_instance: + if not self.service_instance: raise CannotConnectError( host=self.config.vsphere_host, port=self.config.vsphere_port, user=self.config.vsphere_user) @@ -192,10 +195,11 @@ class CrTplHandler(PpBaseObject): self.check_for_temp_tpl_vm() self.select_data_store() self.check_network() + self.create_vm() finally: LOG.debug("Disconnecting from vSphere host {h}:{p} ...".format( h=self.config.vsphere_host, p=self.config.vsphere_port)) - Disconnect(self.server_instance) + Disconnect(self.service_instance) # ------------------------------------------------------------------------- def get_obj(self, content, vimtype, name): @@ -220,11 +224,60 @@ class CrTplHandler(PpBaseObject): return result + # ------------------------------------------------------------------------- + def wait_for_tasks(self, tasks): + + LOG.debug("Waiting for tasks to finish ...") + + property_collector = self.service_instance.content.propertyCollector + task_list = [str(task) for task in tasks] + LOG.debug("Waiting for tasks {} to finish ...".format(task_list)) + # Create filter + obj_specs = [vmodl.query.PropertyCollector.ObjectSpec(obj=task) + for task in tasks] + property_spec = vmodl.query.PropertyCollector.PropertySpec( + type=vim.Task, pathSet=[], all=True) + filter_spec = vmodl.query.PropertyCollector.FilterSpec() + filter_spec.objectSet = obj_specs + filter_spec.propSet = [property_spec] + pcfilter = property_collector.CreateFilter(filter_spec, True) + try: + version, state = None, None + # Loop looking for updates till the state moves to a completed state. + while len(task_list): + update = property_collector.WaitForUpdates(version) + for filter_set in update.filterSet: + time.sleep(0.1) + LOG.debug("Waiting ...") + for obj_set in filter_set.objectSet: + task = obj_set.obj + for change in obj_set.changeSet: + if change.name == 'info': + state = change.val.state + elif change.name == 'info.state': + state = change.val + else: + continue + + if not str(task) in task_list: + continue + + if state == vim.TaskInfo.State.success: + # Remove task from taskList + task_list.remove(str(task)) + elif state == vim.TaskInfo.State.error: + raise task.info.error + # Move to next version + version = update.version + finally: + if pcfilter: + pcfilter.Destroy() + # ------------------------------------------------------------------------- def get_cluster(self, content=None): if not content: - content = self.server_instance.RetrieveContent() + content = self.service_instance.RetrieveContent() cluster_list = self.get_obj_list(content, [vim.ClusterComputeResource]) if not len(cluster_list): @@ -251,7 +304,7 @@ class CrTplHandler(PpBaseObject): def get_tpl_folder(self, vm_folder=None): if not vm_folder: - content = self.server_instance.RetrieveContent() + content = self.service_instance.RetrieveContent() dc = self.get_obj(content, [vim.Datacenter], self.config.dc) vm_folder = dc.vmFolder @@ -269,7 +322,7 @@ class CrTplHandler(PpBaseObject): # ------------------------------------------------------------------------- def ensure_vm_folder(self): - content = self.server_instance.RetrieveContent() + content = self.service_instance.RetrieveContent() dc = self.get_obj(content, [vim.Datacenter], self.config.dc) tpl_vm_folder = self.get_tpl_folder(dc.vmFolder) @@ -299,7 +352,7 @@ class CrTplHandler(PpBaseObject): # ------------------------------------------------------------------------- def get_temp_tpl_vm(self): - content = self.server_instance.RetrieveContent() + content = self.service_instance.RetrieveContent() dc = self.get_obj(content, [vim.Datacenter], self.config.dc) for child in dc.vmFolder.childEntity: @@ -329,7 +382,7 @@ class CrTplHandler(PpBaseObject): # ------------------------------------------------------------------------- def check_network(self): - content = self.server_instance.RetrieveContent() + content = self.service_instance.RetrieveContent() dc = self.get_obj(content, [vim.Datacenter], self.config.dc) LOG.debug("Checking existence of network {!r} ...".format(self.config.network)) @@ -370,7 +423,7 @@ class CrTplHandler(PpBaseObject): "Selecting a SAN based datastore with at least {:0.1f} GiB available " "space.").format(self.config.data_size_gb)) - content = self.server_instance.RetrieveContent() + content = self.service_instance.RetrieveContent() dc = self.get_obj(content, [vim.Datacenter], self.config.dc) ds_list = [] @@ -416,6 +469,110 @@ class CrTplHandler(PpBaseObject): return + # ------------------------------------------------------------------------- + def create_vm(self): + + LOG.info("Creating VM {!r} ...".format(self.config.template_vm)) + + datastore_path = '[' + self.tpl_data_store.summary.name + '] ' + self.config.template_vm + LOG.debug("Datastore path: {!r}".format(datastore_path)) + + vm_file_info = vim.vm.FileInfo( + logDirectory=None, snapshotDirectory=None, + suspendDirectory=None, vmPathName=datastore_path) + + dev_changes = [] + + # Creating SCSI Controller and disk + scsi_ctr_spec = vim.vm.device.VirtualDeviceSpec() + scsi_ctr_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add + scsi_ctr_spec.device = vim.vm.device.VirtualLsiLogicController() + scsi_ctr_spec.device.key = 0 + #scsi_ctr_spec.device.controllerKey = 1000 + scsi_ctr_spec.device.unitNumber = 1 + scsi_ctr_spec.device.sharedBus = 'noSharing' + controller = scsi_ctr_spec.device + + dev_changes.append(scsi_ctr_spec) + + 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() + disk_spec.device.backing.diskMode = 'persistent' + disk_spec.device.backing.fileName = '{ds}/{vm}-sda.vmdk'.format( + ds=datastore_path, vm=self.config.template_vm) + disk_spec.device.unitNumber = 1 + disk_spec.device.key = 1 + disk_spec.device.capacityInKB = self.config.data_size_kb + disk_spec.device.controllerKey = controller.key + + dev_changes.append(disk_spec) + + # Creating network adapter + nic_spec = vim.vm.device.VirtualDeviceSpec() + nic_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add + nic_spec.device = vim.vm.device.VirtualVmxnet3() + nic_spec.device.deviceInfo = vim.Description() + nic_spec.device.deviceInfo.label = 'eth0' + nic_spec.device.deviceInfo.summary = 'Primary network device' + + nic_spec.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo() + nic_spec.device.backing.useAutoDetect = False + nic_spec.device.backing.network = self.tpl_network + nic_spec.device.backing.deviceName = self.config.network + + nic_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo() + nic_spec.device.connectable.startConnected = True + nic_spec.device.connectable.allowGuestControl = True + nic_spec.device.wakeOnLanEnabled = False + nic_spec.device.addressType = 'assigned' + nic_spec.device.macAddress = self.config.mac_address + + dev_changes.append(nic_spec) + + # Graphic Card + video_spec = vim.vm.device.VirtualDeviceSpec() + video_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add + video_spec.device = vim.vm.device.VirtualVideoCard() + video_spec.device.enable3DSupport = False + video_spec.device.graphicsMemorySizeInKB = 256 * 1024 + video_spec.device.numDisplays = 1 + video_spec.device.use3dRenderer = 'automatic' + video_spec.device.videoRamSizeInKB = 32 * 1024 + + dev_changes.append(video_spec) + + # Some other flags + vm_flags = vim.vm.FlagInfo() + vm_flags.diskUuidEnabled = True + + # Some extra options and properties + extra_opts = [] + created_opt = vim.option.OptionValue() + created_opt.key = 'created' + created_opt.value = int(time.time()) + extra_opts.append(created_opt) + + config = vim.vm.ConfigSpec( + name=self.config.template_vm, deviceChange=dev_changes, + flags=vm_flags, extraConfig=extra_opts, + memoryMB=self.config.ram_mb, memoryHotAddEnabled=True, + numCPUs=self.config.num_cpus, cpuHotAddEnabled=True, + files=vm_file_info) + #files=vm_file_info, guestId='OracleLinux7_Guest') + #files=vm_file_info, guestId='OracleLinux7_Guest', version='oel7-4') + + if self.verbose > 2: + LOG.debug("Generated VM config:\n{}".format(pp(config))) + + LOG.debug("Start Creating VM ...") + task = self.tpl_vm_folder.CreateVM_Task( + config=config, pool=self.tpl_cluster.resourcePool) + + self.wait_for_tasks([task]) + # ============================================================================= -- 2.39.5