def _clone_update_mac_addresses(xml): """Update the MAC addresses with new values in the XML descriptor of a cloning domain. The new MAC addresses will be generated randomly, and their values are guaranteed to be distinct from the ones in the original VM. Arguments: xml -- The XML descriptor of the original domain. Return: The XML descriptor <xml> with the new MAC addresses instead of the old ones. """ old_macs = xpath_get_text(xml, XPATH_DOMAIN_MAC) new_macs = [] for mac in old_macs: while True: new_mac = model.vmifaces.VMIfacesModel.random_mac() # make sure the new MAC doesn't conflict with the original VM # and with the new values on the new VM. if new_mac not in (old_macs + new_macs): new_macs.append(new_mac) break xml = xml_item_update(xml, XPATH_DOMAIN_MAC_BY_ADDRESS % mac, new_mac, 'address') return xml
def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0) for key, val in params.items(): if key in VM_STATIC_UPDATE_PARAMS: if key == 'memory': # Libvirt saves memory in KiB. Retrieved xml has memory # in KiB too, so new valeu must be in KiB here val = val * 1024 if type(val) == int: val = str(val) xpath = VM_STATIC_UPDATE_PARAMS[key] new_xml = xml_item_update(new_xml, xpath, val) if 'graphics' in params: new_xml = self._update_graphics(dom, new_xml, params) snapshots_info = [] vm_name = dom.name() conn = self.conn.get() try: if 'name' in params: if state == 'running': msg_args = {'name': vm_name, 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) lflags = libvirt.VIR_DOMAIN_SNAPSHOT_LIST_ROOTS dflags = (libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) for virt_snap in dom.listAllSnapshots(lflags): snapshots_info.append({'xml': virt_snap.getXMLDesc(0), 'current': virt_snap.isCurrent(0)}) self._backup_snapshots(virt_snap, snapshots_info) virt_snap.delete(dflags) # Undefine old vm, only if name is going to change dom.undefine() root = ET.fromstring(new_xml) currentMem = root.find('.currentMemory') if currentMem is not None: root.remove(currentMem) dom = conn.defineXML(ET.tostring(root, encoding="utf-8")) if 'name' in params: self._redefine_snapshots(dom, snapshots_info) except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) if 'name' in params: self._redefine_snapshots(dom, snapshots_info) raise OperationFailed("KCHVM0008E", {'name': vm_name, 'err': e.get_error_message()}) return dom
def _static_vm_update(self, dom, params): old_xml = new_xml = dom.XMLDesc(0) for key, val in params.items(): if key in VM_STATIC_UPDATE_PARAMS: if key == 'memory': # Libvirt saves memory in KiB. Retrieved xml has memory # in KiB too, so new valeu must be in KiB here val = val * 1024 if type(val) == int: val = str(val) xpath = VM_STATIC_UPDATE_PARAMS[key] new_xml = xml_item_update(new_xml, xpath, val) if 'graphics' in params: new_xml = self._update_graphics(dom, new_xml, params) snapshots_info = [] vm_name = dom.name() conn = self.conn.get() try: if 'name' in params: state = DOM_STATE_MAP[dom.info()[0]] if state != 'shutoff': msg_args = {'name': vm_name, 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) lflags = libvirt.VIR_DOMAIN_SNAPSHOT_LIST_ROOTS dflags = (libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) for virt_snap in dom.listAllSnapshots(lflags): snapshots_info.append({'xml': virt_snap.getXMLDesc(0), 'current': virt_snap.isCurrent(0)}) self._backup_snapshots(virt_snap, snapshots_info) virt_snap.delete(dflags) # Undefine old vm, only if name is going to change dom.undefine() root = ET.fromstring(new_xml) currentMem = root.find('.currentMemory') if currentMem is not None: root.remove(currentMem) dom = conn.defineXML(ET.tostring(root, encoding="utf-8")) if 'name' in params: self._redefine_snapshots(dom, snapshots_info) except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) if 'name' in params: self._redefine_snapshots(dom, snapshots_info) raise OperationFailed("KCHVM0008E", {'name': vm_name, 'err': e.get_error_message()}) return dom
def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0) for key, val in params.items(): if key in VM_STATIC_UPDATE_PARAMS: if key == 'memory': # Libvirt saves memory in KiB. Retrieved xml has memory # in KiB too, so new valeu must be in KiB here val = val * 1024 if type(val) == int: val = str(val) xpath = VM_STATIC_UPDATE_PARAMS[key] new_xml = xml_item_update(new_xml, xpath, val) if 'graphics' in params: new_xml = self._update_graphics(dom, new_xml, params) conn = self.conn.get() try: if 'name' in params: if state == 'running': msg_args = {'name': dom.name(), 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) # Undefine old vm, only if name is going to change dom.undefine() root = ET.fromstring(new_xml) currentMem = root.find('.currentMemory') if currentMem is not None: root.remove(currentMem) dom = conn.defineXML(ET.tostring(root, encoding="utf-8")) except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) raise OperationFailed("KCHVM0008E", {'name': dom.name(), 'err': e.get_error_message()}) return dom
def _clone_update_disks(self, xml, rollback): """Clone disks from a virtual machine. The disks are copied as new volumes and the new VM's XML is updated accordingly. Arguments: xml -- The XML descriptor of the original VM + new value for "/domain/uuid". rollback -- A rollback context so the new volumes can be removed if an error occurs during the cloning operation. Return: The XML descriptor <xml> with the new disk paths instead of the old ones. """ # the UUID will be used to create the disk paths uuid = xpath_get_text(xml, XPATH_DOMAIN_UUID)[0] all_paths = xpath_get_text(xml, XPATH_DOMAIN_DISK) vir_conn = self.conn.get() for i, path in enumerate(all_paths): try: vir_orig_vol = vir_conn.storageVolLookupByPath(path) vir_pool = vir_orig_vol.storagePoolLookupByVolume() orig_pool_name = vir_pool.name().decode('utf-8') orig_vol_name = vir_orig_vol.name().decode('utf-8') except libvirt.libvirtError, e: domain_name = xpath_get_text(xml, XPATH_DOMAIN_NAME)[0] raise OperationFailed('KCHVM0035E', {'name': domain_name, 'err': e.message}) orig_pool = self.storagepool.lookup(orig_pool_name) orig_vol = self.storagevolume.lookup(orig_pool_name, orig_vol_name) new_pool_name = orig_pool_name new_pool = orig_pool if orig_pool['type'] in ['dir', 'netfs', 'logical']: # if a volume in a pool 'dir', 'netfs' or 'logical' cannot hold # a new volume with the same size, the pool 'default' should # be used if orig_vol['capacity'] > orig_pool['available']: kimchi_log.warning('storage pool \'%s\' doesn\'t have ' 'enough free space to store image ' '\'%s\'; falling back to \'default\'', orig_pool_name, path) new_pool_name = u'default' new_pool = self.storagepool.lookup(u'default') # ...and if even the pool 'default' cannot hold a new # volume, raise an exception if orig_vol['capacity'] > new_pool['available']: domain_name = xpath_get_text(xml, XPATH_DOMAIN_NAME)[0] raise InvalidOperation('KCHVM0034E', {'name': domain_name}) elif orig_pool['type'] in ['scsi', 'iscsi']: # SCSI and iSCSI always fall back to the storage pool 'default' kimchi_log.warning('cannot create new volume for clone in ' 'storage pool \'%s\'; falling back to ' '\'default\'', orig_pool_name) new_pool_name = u'default' new_pool = self.storagepool.lookup(u'default') # if the pool 'default' cannot hold a new volume, raise # an exception if orig_vol['capacity'] > new_pool['available']: domain_name = xpath_get_text(xml, XPATH_DOMAIN_NAME)[0] raise InvalidOperation('KCHVM0034E', {'name': domain_name}) else: # unexpected storage pool type raise InvalidOperation('KCHPOOL0014E', {'type': orig_pool['type']}) # new volume name: <UUID>-<loop-index>.<original extension> # e.g. 1234-5678-9012-3456-0.img ext = os.path.splitext(path)[1] new_vol_name = u'%s-%d%s' % (uuid, i, ext) task = self.storagevolume.clone(orig_pool_name, orig_vol_name, new_name=new_vol_name) self.task.wait(task['id'], 3600) # 1 h # get the new volume path and update the XML descriptor new_vol = self.storagevolume.lookup(new_pool_name, new_vol_name) xml = xml_item_update(xml, XPATH_DOMAIN_DISK_BY_FILE % path, new_vol['path'], 'file') # remove the new volume should an error occur later rollback.prependDefer(self.storagevolume.delete, new_pool_name, new_vol_name)
class VMModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] self.objstore = kargs['objstore'] self.caps = CapabilitiesModel(**kargs) self.vmscreenshot = VMScreenshotModel(**kargs) self.users = import_class('kimchi.model.users.UsersModel')(**kargs) self.groups = import_class('kimchi.model.groups.GroupsModel')(**kargs) self.vms = VMsModel(**kargs) self.task = TaskModel(**kargs) self.storagepool = model.storagepools.StoragePoolModel(**kargs) self.storagevolume = model.storagevolumes.StorageVolumeModel(**kargs) self.storagevolumes = model.storagevolumes.StorageVolumesModel(**kargs) cls = import_class('kimchi.model.vmsnapshots.VMSnapshotModel') self.vmsnapshot = cls(**kargs) cls = import_class('kimchi.model.vmsnapshots.VMSnapshotsModel') self.vmsnapshots = cls(**kargs) def update(self, name, params): dom = self.get_vm(name, self.conn) dom = self._static_vm_update(dom, params) self._live_vm_update(dom, params) return dom.name().decode('utf-8') def clone(self, name): """Clone a virtual machine based on an existing one. The new virtual machine will have the exact same configuration as the original VM, except for the name, UUID, MAC addresses and disks. The name will have the form "<name>-clone-<number>", with <number> starting at 1; the UUID will be generated randomly; the MAC addresses will be generated randomly with no conflicts within the original and the new VM; and the disks will be new volumes [mostly] on the same storage pool, with the same content as the original disks. The storage pool 'default' will always be used when cloning SCSI and iSCSI disks and when the original storage pool cannot hold the new volume. An exception will be raised if the virtual machine <name> is not shutoff, if there is no available space to copy a new volume to the storage pool 'default' (when there was also no space to copy it to the original storage pool) and if one of the virtual machine's disks belong to a storage pool not supported by Kimchi. Parameters: name -- The name of the existing virtual machine to be cloned. Return: A Task running the clone operation. """ name = name.decode('utf-8') # VM must be shutoff in order to clone it info = self.lookup(name) if info['state'] != u'shutoff': raise InvalidParameter('KCHVM0033E', {'name': name}) # the new VM's name will be used as the Task's 'target_uri' so it needs # to be defined now. vms_being_created = [] # lookup names of VMs being created right now with self.objstore as session: task_names = session.get_list('task') for tn in task_names: t = session.get('task', tn) if t['target_uri'].startswith('/vms/'): uri_name = t['target_uri'][5:] # 5 = len('/vms/') vms_being_created.append(uri_name) current_vm_names = self.vms.get_list() + vms_being_created new_name = get_next_clone_name(current_vm_names, name) # create a task with the actual clone function taskid = add_task(u'/vms/%s' % new_name, self._clone_task, self.objstore, {'name': name, 'new_name': new_name}) return self.task.lookup(taskid) def _clone_task(self, cb, params): """Asynchronous function which performs the clone operation. Parameters: cb -- A callback function to signal the Task's progress. params -- A dict with the following values: "name": the name of the original VM. "new_name": the name of the new VM. """ name = params['name'] new_name = params['new_name'] vir_conn = self.conn.get() # fetch base XML cb('reading source VM XML') try: vir_dom = vir_conn.lookupByName(name) flags = libvirt.VIR_DOMAIN_XML_SECURE xml = vir_dom.XMLDesc(flags).decode('utf-8') except libvirt.libvirtError, e: raise OperationFailed('KCHVM0035E', {'name': name, 'err': e.message}) # update UUID cb('updating VM UUID') old_uuid = xpath_get_text(xml, XPATH_DOMAIN_UUID)[0] new_uuid = unicode(uuid.uuid4()) xml = xml_item_update(xml, './uuid', new_uuid) # update MAC addresses cb('updating VM MAC addresses') xml = self._clone_update_mac_addresses(xml) with RollbackContext() as rollback: # copy disks cb('copying VM disks') xml = self._clone_update_disks(xml, rollback) # update objstore entry cb('updating object store') self._clone_update_objstore(old_uuid, new_uuid, rollback) # update name cb('updating VM name') xml = xml_item_update(xml, './name', new_name) # create new guest cb('defining new VM') try: vir_conn.defineXML(xml) except libvirt.libvirtError, e: raise OperationFailed('KCHVM0035E', {'name': name, 'err': e.message}) rollback.commitAll()
def volWipePattern(vol, algorithm, flags=0): new_xml = xml_item_update(vol.XMLDesc(0), './allocation', '0') vol.delete(0) pool = vol.storagePoolLookupByVolume() pool.createXML(new_xml)
def volResize(vol, size, flags=0): new_xml = xml_item_update(vol.XMLDesc(0), './capacity', str(size)) vol.delete(0) pool = vol.storagePoolLookupByVolume() pool.createXML(new_xml)