def _take_snapshot(the_vm, dump_memory=True, quiesce=False, description=''): """Take a new snapshot of the virtual machine. :Returns: Tuple (snap_id, created_timestamp, expires_timesampt) :param the_vm: The virtual machine with snapshots to delete :type the_vm: vim.VirtualMachine :param dump_memory: When True, includes the running memory state of the VM in the snapshot. Default True. :type dump_memory: Boolean :param quiesce: When the VM has VMwareTools installed, setting this flag to True waits to create a snapshot until after buffers in the VM are flushed to disk. Useless to use when ``dump_memory`` is set to True. Default False :type quiesce: Boolean """ created = int(time.time()) snap_id = '{}'.format(uuid.uuid4())[:6] expires = created + const.VLAB_SNAPSHOT_EXPIRES_AFTER snap_name = '{}_{}_{}'.format(snap_id, created, expires) consume_task(the_vm.CreateSnapshot(snap_name, description, dump_memory, quiesce), timeout=1800) return snap_id, created, expires
def reap_snapshots(vcenter, logger): """Walk the VMs owned by users in vLab, and delete all expired VM snapshots. :Returns: None :param vcenter: The vCenter server that hosts the user's Virtual Machines :type vcenter: vlab_inf_common.vmware.vCenter :param logger: Handles logging messages while the reaper runs :type logger: logging.Logger """ all_users = vcenter.get_by_name(name=const.INF_VCENTER_USERS_DIR, vimtype=vim.Folder) for username in all_users.childEntity: vms = vcenter.get_by_name(name=username.name, vimtype=vim.Folder) for vm in vms.childEntity: if vm.snapshot: vm_snaps = _get_snapshots(vm.snapshot.rootSnapshotList) for snap in vm_snaps: if is_expired(snap.name): logger.info( "deleteing snap {} of VM {} owned by {}".format( snap, vm.name, username)) consume_task( snap.snapshot.RemoveSnapshot_Task( removeChildren=False))
def add_unique_params(the_vm, ip_config): """Superna cannot boot without some required network parameters configured. :Returns: None :param the_vm: The newly created Superna server. :type the_vm: vim.VirtualMachine :param ip_config: The IPv4 network configuration for the Avamar instance. :type ip_config: Dictionary """ vapp_spec = vim.vApp.VmConfigSpec() mapping = { 'eth0.ipv4.ip': ip_config['static-ip'], 'eth0.ipv4.netmask': ip_config['netmask'], 'eth0.ipv4.gateway': ip_config['default-gateway'], 'hostname': the_vm.name, 'nameservers': ' '.join(ip_config['dns']) } for prop in the_vm.config.vAppConfig.property: if prop.id in mapping: config = vim.vApp.PropertySpec() config.operation = 'edit' config.info = prop config.info.value = mapping[prop.id] vapp_spec.property.append(config) spec = vim.vm.ConfigSpec() spec.vAppConfig = vapp_spec task = the_vm.ReconfigVM_Task(spec) consume_task(task)
def delete_deployment(username, machine_name, logger): """Unregister and destroy a user's Deployment :Returns: None :param username: The user who wants to delete their jumpbox :type username: String :param machine_name: The name of the VM to delete :type machine_name: String :param logger: An object for logging messages :type logger: logging.LoggerAdapter """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER, \ password=const.INF_VCENTER_PASSWORD) as vcenter: folder = vcenter.get_by_name(name=username, vimtype=vim.Folder) tasks = [] for entity in folder.childEntity: info = virtual_machine.get_info(vcenter, entity, username) if info['meta'].get('deployment', False) == True: logger.debug('powering off VM %s', entity.name) virtual_machine.power(entity, state='off') delete_task = entity.Destroy_Task() tasks.append(delete_task) if tasks: logger.debug('blocking while VMs are being destroyed') for task in tasks: consume_task(task) else: raise ValueError('No {} named {} found'.format('deployment', machine_name))
def create_network(name, vlan_id, switch_name): """Create a new network for VMs. :Returns: String (error message) :param name: The name of the new distributed virtual portgroup :type name: String :param vlan_id: The vLAN tag id of the new dv portgroup :type vlan_id: Integer :param switch_name: The name of the switch to add the new vLAN network to :type switch_name: String """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER,\ password=const.INF_VCENTER_PASSWORD) as vcenter: try: switch = vcenter.dv_switches[switch_name] except KeyError: available = list(vcenter.dv_switches.keys()) msg = 'No such switch: {}, Available: {}'.format( switch_name, available) raise ValueError(msg) spec = get_dv_portgroup_spec(name, vlan_id) task = switch.AddDVPortgroup_Task([spec]) try: consume_task(task, timeout=300) error = '' except RuntimeError as doh: error = '{}'.format(doh) return error
def delete_windows(username, machine_name, logger): """Unregister and destroy a user's Windows :Returns: None :param username: The user who wants to delete their jumpbox :type username: String :param machine_name: The name of the VM to delete :type machine_name: String :param logger: An object for logging messages :type logger: logging.LoggerAdapter """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER, \ password=const.INF_VCENTER_PASSWORD) as vcenter: folder = vcenter.get_by_name(name=username, vimtype=vim.Folder) for entity in folder.childEntity: if entity.name == machine_name: info = virtual_machine.get_info(vcenter, entity, username) if info['meta']['component'] == 'Windows': logger.debug('powering off VM') virtual_machine.power(entity, state='off') delete_task = entity.Destroy_Task() logger.debug('blocking while VM is being destroyed') consume_task(delete_task) break else: raise ValueError('No {} named {} found'.format( 'windows', machine_name))
def nuke_folder(folder, timeout=300): """Delete a user's folder :Returns: None :param folder: **Required** The user's folder to delete :type folder: vim.Folder :param timeout: How long to wait for the operation to complete :type timeout: Integer """ task = folder.Destroy() consume_task(task, timeout=timeout)
def config_vm(the_vm): """Enable hardware-assisted virtualization so 64-bit OSes can run on the virtual ESXi host. :Returns: None :param the_vm: The new ESXi virtual machine object :type the_vm: vim.VirtualMachine """ spec = vim.vm.ConfigSpec() spec.nestedHVEnabled = True task = the_vm.ReconfigVM_Task(spec) consume_task(task)
def _setup_jumpbox(vcenter, the_vm, username): """Configure the Jumpbox for the end user :Returns: None :param vcenter: The instantiated connection to vCenter :type vcenter: vlab_inf_common.vmware.vCenter :param the_vm: The new gateway :type the_vm: vim.VirtualMachine """ # Add the note about the type & version of Jumpbox being used spec = vim.vm.ConfigSpec() spec.annotation = 'ubuntu=18.04' task = the_vm.ReconfigVM_Task(spec) consume_task(task) # Create an admin user with the same username as the end-user cmd = '/usr/bin/sudo' # SHA 512 version of the letter 'a' (plus the $6$ to denote SHA 512) pw = '$6$qM5mj4O0$x8l6R4T4sH1HJgYt9dw3n2pYO8E0Rs/sqlCfts5/p8o8ZK8aBjfHRlh37xnxIfPZBp.ErfBgnSJcauzP2mxBx.' args = "/usr/sbin/useradd --shell /bin/bash --password '{1}' --create-home --groups sudo --home-dir /home/{0} {0} ".format( username, pw) result = virtual_machine.run_command(vcenter, the_vm, cmd, user='******', password='******', arguments=args) if result.exitCode: error = 'Failed to create user {} in newly deployed jumpbox'.format( username) raise RuntimeError(error) # Make the Ubuntu GNOME desktop the default used by xRDP # https://www.hiroom2.com/2018/04/29/ubuntu-1804-xrdp-gnome-en/ cmd2 = '/usr/bin/sudo' args2 = '/bin/cp /etc/xrdp/xsessionrc /home/{}/.xsessionrc'.format( username) result2 = virtual_machine.run_command(vcenter, the_vm, cmd2, user='******', password='******', arguments=args2) if result2.exitCode: error = 'Failed to create .xsessionrc file in {} homedir'.format( username) raise RuntimeError(error)
def adjust_cpu(the_vm, cpu_count): """Set the number of CPUs for a VM **IMPORTANT** Make sure your VM is powered off before calling this function, otherwise it'll fail. :param the_vm: The virtual machine to adjust CPU count on :type the_vm: vim.VirtualMachine :param cpu_count: The number of CPU cores to allocate to the VM :type cpu_count: Integer """ config_spec = vim.vm.ConfigSpec() config_spec.numCPUs = cpu_count consume_task(the_vm.Reconfigure(config_spec))
def delete_jumpbox(username): """Unregister and destroy the user's jumpbox :Returns: None :param username: The user who wants to delete their jumpbox :type username: String """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER, \ password=const.INF_VCENTER_PASSWORD) as vcenter: folder = vcenter.get_by_name(name=username, vimtype=vim.Folder) for entity in folder.childEntity: if entity.name == COMPONENT_NAME: logger.debug('powering off VM') virtual_machine.power(entity, state='off') delete_task = entity.Destroy_Task() logger.debug('blocking while VM is being destroyed') consume_task(delete_task)
def _deleted_old_snaps(the_vm, logger): """Delete all snapshots such that const.VLAB_SNAP_CREATED is not exceeded. Returns the number of snapshots deleted. :Returns: Integer :param the_vm: The virtual machine with snapshots to delete :type the_vm: vim.VirtualMachine """ all_snaps = _get_snapshots(the_vm.snapshot.rootSnapshotList) all_snaps = sorted( all_snaps, key=lambda x: int(x.name.split('_')[const.VLAB_SNAP_CREATED])) delete_count = len(all_snaps) - const.VLAB_MAX_SNAPSHOTS to_delete = [] for _ in range(delete_count): to_delete.append(all_snaps.pop(0)) for snap in to_delete: consume_task(snap.snapshot.RemoveSnapshot_Task(removeChildren=False)) return delete_count
def adjust_ram(the_vm, mb_of_ram): """Set the amount of RAM for a VM **IMPORTANT** Most VMs are required to be powered off in order to adjust RAM. Unless you know that your guest OS supports hot-swap RAM, power your VM off before changing how much RAM it has. :Returns: None :param the_vm: The virtual machine to adjust RAM on :type the_vm: vim.VirtualMachine :param mb_of_ram: The number of MB of RAM/memory to give the virtual machine :type mb_of_ram: Integer """ config_spec = vim.vm.ConfigSpec() config_spec.memoryMB = mb_of_ram consume_task(the_vm.Reconfigure(config_spec))
def delete_snapshot(username, snap_id, machine_name, logger): """Destroy a snapshot :Returns: None :param username: The user who wants to delete their jumpbox :type username: String :param snap_id: The snapshot to destroy :type snap_id: Integer :param machine_name: The name of the virtual machine which owns the snapshot :type machine_name: String :param logger: An object for logging messages :type logger: logging.LoggerAdapter """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER, \ password=const.INF_VCENTER_PASSWORD) as vcenter: folder = vcenter.get_by_name(name=username, vimtype=vim.Folder) for entity in folder.childEntity: if entity.name == machine_name: if entity.snapshot: for snap in _get_snapshots( entity.snapshot.rootSnapshotList): snap_data = snap.name.split('_') if snap_data[const.VLAB_SNAP_ID] == snap_id: logger.info('Deleting snapshot {} from {}'.format( snap.name, machine_name)) consume_task( snap.snapshot.RemoveSnapshot_Task( removeChildren=False)) # return exits nested for-loop; break just stop immediate parent loop return None else: error = 'VM has no snapshot by ID {}'.format(snap_id) raise ValueError(error) else: error = 'No VM named {} found in inventory'.format(machine_name) logger.info(error) raise ValueError(error)
def delete_gateway(username, logger): """Unregister and destroy the defaultGateway virtual machine :Returns: None :param username: The user who wants to delete their defaultGateway :type username: String :param logger: An object for logging messages :type logger: logging.LoggerAdapter """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER, \ password=const.INF_VCENTER_PASSWORD) as vcenter: folder = vcenter.get_by_name(name=username, vimtype=vim.Folder) for entity in folder.childEntity: if entity.name == COMPONENT_NAME: logger.debug('powering off VM') virtual_machine.power(entity, state='off') delete_task = entity.Destroy_Task() logger.debug('blocking while VM is being destroyed') consume_task(delete_task)
def power(the_vm, state, timeout=600): """Turn on/off/restart a given virtual machine. Turning off and restarting do **not** perform graceful shutdowns; it's like pulling the power cable. This method blocks until the VM is in the requested power state. :Returns: Boolean :param the_vm: The pyVmomi Virtual machine object :type the_vm: vim.VirtualMachine :param state: The power state to put the VM into. Valid values are "on" "off" and "restart" :type state: Enum/String :param timeout: Optional - How long (in milliseconds) to block waiting on a given power state :type timeout: Integer """ valid_states = {'on', 'off', 'restart'} if state not in valid_states: error = 'state must be one of {}, supplied {}'.format( valid_states, state) raise ValueError(error) vm_power_state = the_vm.runtime.powerState.lower().replace('powered', '') if vm_power_state == state: return True elif (state == 'on') or (vm_power_state == 'off' and state == 'restart'): task = the_vm.PowerOn() elif state == 'off': task = the_vm.PowerOff() elif state == 'restart': task = the_vm.ResetVM_Task() try: consume_task(task, timeout=timeout) except RuntimeError: # task failed or timed out return False return True
def change_network(the_vm, network, adapter_label='Network adapter 1'): """Update the VM; replace existing network with the supplied network. :Returns: None :param the_vm: The virtual machine to update :type the_vm: vim.VirtualMachine :param network: The new network the VM should be connected to :type network: vim.Network :param adapter_label: The name of the virtual NIC to connect to a new device :type adapter_label: String """ devices = [ x for x in the_vm.config.hardware.device if x.deviceInfo.label == adapter_label ] if not devices: error = "VM has no network adapter named {}".format(adapter_label) raise RuntimeError(error) else: device = devices[0] nicspec = vim.vm.device.VirtualDeviceSpec() nicspec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit nicspec.device = device nicspec.device.wakeOnLanEnabled = True dvs_port_connection = vim.dvs.PortConnection() dvs_port_connection.portgroupKey = network.key dvs_port_connection.switchUuid = network.config.distributedVirtualSwitch.uuid nicspec.device.backing = vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo( ) nicspec.device.backing.port = dvs_port_connection nicspec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo() nicspec.device.connectable.startConnected = True nicspec.device.connectable.allowGuestControl = True nicspec.device.connectable.connected = True config_spec = vim.vm.ConfigSpec(deviceChange=[nicspec]) consume_task(the_vm.ReconfigVM_Task(config_spec))
def _add_database_disk(the_vm, disk_size): """Add a VMDK to the new DataIQ instance to store it's database. :Returns: None :Rasies: RuntimeError :param the_vm: The new DataIQ machine :type the_vm: vim.VirtualMachine :param disk_size: The number of GB to make the disk :type disk_size: Integer """ spec = vim.vm.ConfigSpec() unit_number = 0 for dev in the_vm.config.hardware.device: if hasattr(dev.backing, 'fileName'): unit_number = int(dev.unitNumber) + 1 # unitNumber 7 is reserved for the SCSI controller if unit_number == 7: unit_number += 1 if unit_number >= 16: raise RuntimeError('VM cannot have 16 VMDKs') if unit_number == 0: raise RuntimeError('Unable to find any VMDKs for VM') dev_changes = [] 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.thinProvisioned = True disk_spec.device.backing.diskMode = 'persistent' disk_spec.device.unitNumber = unit_number disk_spec.device.capacityInKB = int(disk_size) * 1024 * 1024 disk_spec.device.controllerKey = 1000 dev_changes.append(disk_spec) spec.deviceChange = dev_changes consume_task(the_vm.ReconfigVM_Task(spec=spec))
def set_meta(the_vm, meta_data): """Truncate and replace the meta data associated with a given virtual machine. :Returns: None :Raises: ValueError - when invalid meta data supplied :param the_vm: The virtual machine to assign the meta data to :type the_vm: vim.VirtualMachine :param meta_data: The extra information to associate to the virtual machine :type meta_data: Dictionary """ expected = {'component', 'created', 'version', 'generation', 'configured'} provided = set(meta_data.keys()) if not expected == provided: error = "Invalid meta data schema. Supplied: {}, Required: {}".format( provided, expected) raise ValueError(error) spec = vim.vm.ConfigSpec() spec_info = ujson.dumps(meta_data) spec.annotation = spec_info task = the_vm.ReconfigVM_Task(spec) consume_task(task)
def delete_network(name): """Destroy a vLAN network :Returns: None :Raises: ValueError :param name: The name of the network to destroy :type name: String """ with vCenter(host=const.INF_VCENTER_SERVER, user=const.INF_VCENTER_USER, \ password=const.INF_VCENTER_PASSWORD) as vcenter: try: network = vcenter.networks[name] except KeyError: msg = 'No such vLAN exists: {}'.format(name) raise ValueError(msg) try: task = network.Destroy_Task() consume_task(task, timeout=300) except RuntimeError: msg = "Network {} in use. Must delete VMs using network before deleting network.".format( name) raise ValueError(msg)