def _vm_get_graphics(self, name): dom = self.get_vm(name, self.conn) xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE) expr = "/domain/devices/graphics/@type" res = xpath_get_text(xml, expr) graphics_type = res[0] if res else None expr = "/domain/devices/graphics/@listen" res = xpath_get_text(xml, expr) graphics_listen = res[0] if res else None graphics_port = graphics_passwd = graphics_passwdValidTo = None if graphics_type: expr = "/domain/devices/graphics[@type='%s']/@port" res = xpath_get_text(xml, expr % graphics_type) graphics_port = int(res[0]) if res else None expr = "/domain/devices/graphics[@type='%s']/@passwd" res = xpath_get_text(xml, expr % graphics_type) graphics_passwd = res[0] if res else None expr = "/domain/devices/graphics[@type='%s']/@passwdValidTo" res = xpath_get_text(xml, expr % graphics_type) if res: to = time.mktime(time.strptime(res[0], '%Y-%m-%dT%H:%M:%S')) graphics_passwdValidTo = to - time.mktime(time.gmtime()) return (graphics_type, graphics_listen, graphics_port, graphics_passwd, graphics_passwdValidTo)
def test_ip_xml(self): """ Test network ip xml """ dhcp_range = {"start": "192.168.122.100", "end": "192.168.122.254"} params = {} dhcp = nxml._get_dhcp_elem(**params) self.assertEquals(None, dhcp) params["net"] = "192.168.122.0/255.255.255.0" params["dhcp"] = {'range': dhcp_range} xml = ET.tostring(nxml._get_ip_elem(**params)) start = xpath_get_text(xml, "/ip/dhcp/range/@start")[0] end = xpath_get_text(xml, "/ip/dhcp/range/@end")[0] self.assertEquals(dhcp_range['start'], start) self.assertEquals(dhcp_range['end'], end) address = xpath_get_text(xml, "/ip/@address")[0] netmask = xpath_get_text(xml, "/ip/@netmask")[0] self.assertEquals(address, params["net"].split("/")[0]) self.assertEquals(netmask, params["net"].split("/")[1]) # test _get_ip_xml can accepts strings: '192.168.122.0/24', # which is same as "192.168.122.0/255.255.255.0" params["net"] = "192.168.122.0/24" xml = ET.tostring(nxml._get_ip_elem(**params)) netmask = xpath_get_text(xml, "/ip/@netmask")[0] self.assertEquals(netmask, str(ipaddr.IPNetwork(params["net"]).netmask))
def _vm_update_access_metadata(self, dom, params): users = groups = None if "users" in params: users = params["users"] invalid_users = set(users) - set(self.users.get_list()) if len(invalid_users) != 0: raise InvalidParameter("KCHVM0027E", {'users': ", ".join(invalid_users)}) if "groups" in params: groups = params["groups"] invalid_groups = set(groups) - set(self.groups.get_list()) if len(invalid_groups) != 0: raise InvalidParameter("KCHVM0028E", {'groups': ", ".join(invalid_groups)}) if users is None and groups is None: return access_xml = (get_metadata_node(dom, "access") or """<access></access>""") old_users = xpath_get_text(access_xml, "/access/user") old_groups = xpath_get_text(access_xml, "/access/group") users = old_users if users is None else users groups = old_groups if groups is None else groups node = self._build_access_elem(users, groups) set_metadata_node(dom, node)
def test_dhcp_xml(self): """ Test network dhcp xml """ dhcp_range = {"start": "192.168.122.100", "end": "192.168.122.254"} host1 = {"mac": "00:16:3e:77:e2:ed", "name": "foo.example.com", "ip": "192.168.122.10"} host2 = {"mac": "00:16:3e:3e:a9:1a", "name": "bar.example.com", "ip": "192.168.122.11"} params = {} dhcp = nxml._get_dhcp_elem(**params) self.assertEquals(None, dhcp) params["range"] = dhcp_range xml = ET.tostring(nxml._get_dhcp_elem(**params)) start = xpath_get_text(xml, "/dhcp/range/@start") end = xpath_get_text(xml, "/dhcp/range/@end") self.assertEquals(dhcp_range['start'], start[0]) self.assertEquals(dhcp_range['end'], end[0]) params["hosts"] = [host1, host2] xml = ET.tostring(nxml._get_dhcp_elem(**params)) ip = xpath_get_text(xml, "/dhcp/host/@ip") self.assertEquals(ip, [host1['ip'], host2['ip']])
def lookup(self, name): dom = self.get_vm(name, self.conn) info = dom.info() state = DOM_STATE_MAP[info[0]] screenshot = None # (type, listen, port, passwd, passwdValidTo) graphics = self._vm_get_graphics(name) graphics_port = graphics[2] graphics_port = graphics_port if state == 'running' else None try: if state == 'running' and self._has_video(dom): screenshot = self.vmscreenshot.lookup(name) elif state == 'shutoff': # reset vm stats when it is powered off to avoid sending # incorrect (old) data stats[dom.UUIDString()] = {} except NotFoundError: pass with self.objstore as session: try: extra_info = session.get('vm', dom.UUIDString()) except NotFoundError: extra_info = {} icon = extra_info.get('icon') vm_stats = stats.get(dom.UUIDString(), {}) res = {} res['cpu_utilization'] = vm_stats.get('cpu', 0) res['net_throughput'] = vm_stats.get('net_io', 0) res['net_throughput_peak'] = vm_stats.get('max_net_io', 100) res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100) access_xml = (get_metadata_node(dom, "access") or """<access></access>""") users = xpath_get_text(access_xml, "/access/user") groups = xpath_get_text(access_xml, "/access/group") return {'name': name, 'state': state, 'stats': res, 'uuid': dom.UUIDString(), 'memory': info[2] >> 10, 'cpus': info[3], 'screenshot': screenshot, 'icon': icon, # (type, listen, port, passwd, passwdValidTo) 'graphics': {"type": graphics[0], "listen": graphics[1], "port": graphics_port, "passwd": graphics[3], "passwdValidTo": graphics[4]}, 'users': users, 'groups': groups, 'access': 'full', 'persistent': True if dom.isPersistent() else False }
def test_to_xml(self): graphics = {'type': 'spice', 'listen': '127.0.0.1'} vm_uuid = str(uuid.uuid4()).replace('-', '') t = VMTemplate({'name': 'test-template', 'cdrom': self.iso}) xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) self.assertEquals(vm_uuid, xpath_get_text(xml, "/domain/uuid")[0]) self.assertEquals('test-vm', xpath_get_text(xml, "/domain/name")[0]) expr = "/domain/devices/graphics/@type" self.assertEquals(graphics['type'], xpath_get_text(xml, expr)[0]) expr = "/domain/devices/graphics/@listen" self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0])
def get_network_from_xml(xml): address = xpath_get_text(xml, "/network/ip/@address") address = address and address[0] or '' netmask = xpath_get_text(xml, "/network/ip/@netmask") netmask = netmask and netmask[0] or '' net = address and netmask and "/".join([address, netmask]) or '' dhcp_start = xpath_get_text(xml, "/network/ip/dhcp/range/@start") dhcp_start = dhcp_start and dhcp_start[0] or '' dhcp_end = xpath_get_text(xml, "/network/ip/dhcp/range/@end") dhcp_end = dhcp_end and dhcp_end[0] or '' dhcp = {'start': dhcp_start, 'end': dhcp_end} forward_mode = xpath_get_text(xml, "/network/forward/@mode") forward_mode = forward_mode and forward_mode[0] or '' forward_if = xpath_get_text(xml, "/network/forward/interface/@dev") forward_pf = xpath_get_text(xml, "/network/forward/pf/@dev") bridge = xpath_get_text(xml, "/network/bridge/@name") bridge = bridge and bridge[0] or '' return { 'subnet': net, 'dhcp': dhcp, 'bridge': bridge, 'forward': { 'mode': forward_mode, 'interface': forward_if, 'pf': forward_pf } }
def delete(self, name): conn = self.conn.get() dom = self.get_vm(name, self.conn) self._vmscreenshot_delete(dom.UUIDString()) paths = self._vm_get_disk_paths(dom) info = self.lookup(name) if info['state'] == 'running': self.poweroff(name) try: dom.undefine() except libvirt.libvirtError as e: raise OperationFailed("KCHVM0021E", {'name': name, 'err': e.get_error_message()}) for path in paths: vol = conn.storageVolLookupByPath(path) pool = vol.storagePoolLookupByVolume() xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type not in READONLY_POOL_TYPE: vol.delete(0) try: with self.objstore as session: session.delete('vm', dom.UUIDString(), ignore_missing=True) except Exception as e: # It is possible to delete vm without delete its database info kimchi_log.error('Error deleting vm information from database: ' '%s', e.message) vnc.remove_proxy_token(name)
def test_forward_xml(self): """ Test network forward xml """ params = {"mode": None} forward = nxml._get_forward_elem(**params) self.assertEquals(None, forward) params["mode"] = 'nat' params["dev"] = 'eth0' xml = ET.tostring(nxml._get_forward_elem(**params)) mode = xpath_get_text(xml, "/forward/@mode")[0] dev = xpath_get_text(xml, "/forward/@dev")[0] self.assertEquals(params['mode'], mode) self.assertEquals(params['dev'], dev)
def lookup(self, pool, name): vol = StorageVolumeModel.get_storagevolume(pool, name, self.conn) path = vol.path() info = vol.info() xml = vol.XMLDesc(0) try: fmt = xpath_get_text(xml, "/volume/target/format/@type")[0] except IndexError: # Not all types of libvirt storage can provide volume format # infomation. When there is no format information, we assume # it's 'raw'. fmt = 'raw' ref_cnt = get_disk_ref_cnt(self.objstore, self.conn, path) res = dict(type=VOLUME_TYPE_MAP[info[0]], capacity=info[1], allocation=info[2], path=path, ref_cnt=ref_cnt, format=fmt) if fmt == 'iso': if os.path.islink(path): path = os.path.join(os.path.dirname(path), os.readlink(path)) os_distro = os_version = 'unknown' try: iso_img = IsoImage(path) os_distro, os_version = iso_img.probe() bootable = True except IsoFormatError: bootable = False res.update( dict(os_distro=os_distro, os_version=os_version, path=path, bootable=bootable)) return res
def lookup(self, pool, name): vol = self._get_storagevolume(pool, name) path = vol.path() info = vol.info() xml = vol.XMLDesc(0) try: fmt = xpath_get_text(xml, "/volume/target/format/@type")[0] except IndexError: # Not all types of libvirt storage can provide volume format # infomation. When there is no format information, we assume # it's 'raw'. fmt = 'raw' ref_cnt = get_disk_ref_cnt(self.objstore, self.conn, path) res = dict(type=VOLUME_TYPE_MAP[info[0]], capacity=info[1], allocation=info[2], path=path, ref_cnt=ref_cnt, format=fmt) if fmt == 'iso': if os.path.islink(path): path = os.path.join(os.path.dirname(path), os.readlink(path)) os_distro = os_version = 'unknown' try: iso_img = IsoImage(path) os_distro, os_version = iso_img.probe() bootable = True except IsoFormatError: bootable = False res.update( dict(os_distro=os_distro, os_version=os_version, path=path, bootable=bootable)) return res
def deactivate(self, name): if self._pool_used_by_template(name): raise InvalidOperation('KCHPOOL0034E', {'name': name}) pool = self.get_storagepool(name, self.conn) # FIXME: nfs workaround - do not try to deactivate a NFS pool # if the NFS server is not reachable. xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type == 'netfs' and not self._nfs_status_online(pool): # block the user from dactivating the pool. source = self._get_storage_source(pool_type, xml) raise OperationFailed("KCHPOOL0033E", {'name': name, 'server': source['addr']}) return try: persistent = pool.isPersistent() pool.destroy() except libvirt.libvirtError as e: raise OperationFailed("KCHPOOL0010E", {'name': name, 'err': e.get_error_message()}) # If pool was not persistent, then it was erased by destroy() and # must return nothing here, to trigger _redirect() and avoid errors if not persistent: return ""
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 deactivate(self, name): if self._pool_used_by_template(name): raise InvalidOperation('KCHPOOL0034E', {'name': name}) pool = self.get_storagepool(name, self.conn) # FIXME: nfs workaround - do not try to deactivate a NFS pool # if the NFS server is not reachable. xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type == 'netfs' and not self._nfs_status_online(pool): # block the user from dactivating the pool. source = self._get_storage_source(pool_type, xml) raise OperationFailed("KCHPOOL0033E", { 'name': name, 'server': source['addr'] }) return try: persistent = pool.isPersistent() pool.destroy() except libvirt.libvirtError as e: raise OperationFailed("KCHPOOL0010E", { 'name': name, 'err': e.get_error_message() }) # If pool was not persistent, then it was erased by destroy() and # must return nothing here, to trigger _redirect() and avoid errors if not persistent: return ""
def test_to_xml(self): graphics = {"type": "spice", "listen": "127.0.0.1"} vm_uuid = str(uuid.uuid4()).replace("-", "") if os.uname()[4] in ["ppc", "ppc64", "ppc64le"]: maxmem = 3328 else: maxmem = 3072 t = VMTemplate({"name": "test-template", "cdrom": self.iso, "max_memory": maxmem << 10}) xml = t.to_vm_xml("test-vm", vm_uuid, graphics=graphics) self.assertEquals(vm_uuid, xpath_get_text(xml, "/domain/uuid")[0]) self.assertEquals("test-vm", xpath_get_text(xml, "/domain/name")[0]) expr = "/domain/devices/graphics/@type" self.assertEquals(graphics["type"], xpath_get_text(xml, expr)[0]) expr = "/domain/devices/graphics/@listen" self.assertEquals(graphics["listen"], xpath_get_text(xml, expr)[0]) expr = "/domain/maxMemory/@slots" self.assertEquals("2", xpath_get_text(xml, expr)[0])
def lookup(self, name): pool = self.get_storagepool(name, self.conn) info = pool.info() autostart = True if pool.autostart() else False persistent = True if pool.isPersistent() else False xml = pool.XMLDesc(0) path = xpath_get_text(xml, "/pool/target/path")[0] pool_type = xpath_get_text(xml, "/pool/@type")[0] source = self._get_storage_source(pool_type, xml) # FIXME: nfs workaround - prevent any libvirt operation # for a nfs if the corresponding NFS server is down. if pool_type == 'netfs' and not self._nfs_status_online(pool): kimchi_log.debug( "NFS pool %s is offline, reason: NFS " "server %s is unreachable.", name, source['addr']) # Mark state as '4' => inaccessible. info[0] = 4 # skip calculating volumes nr_volumes = 0 else: nr_volumes = self._get_storagepool_vols_num(pool) res = { 'state': POOL_STATE_MAP[info[0]], 'path': path, 'source': source, 'type': pool_type, 'autostart': autostart, 'capacity': info[1], 'allocated': info[2], 'available': info[3], 'nr_volumes': nr_volumes, 'persistent': persistent } if not pool.isPersistent(): # Deal with deep scan generated pool try: with self.objstore as session: task_id = session.get('scanning', name) res['task_id'] = str(task_id) res['type'] = 'kimchi-iso' except NotFoundError: # User created normal pool pass return res
def _get_ipaddr_info(libvirt_interface_xml): search_ip = \ xpath_get_text(libvirt_interface_xml, "/interface/protocol[@family='ipv4']" "/ip/@address") if not len(search_ip): return '', '' search_prefix = \ xpath_get_text(libvirt_interface_xml, "/interface/protocol[@family='ipv4']" "/ip/@prefix") ip_obj = ipaddr.IPv4Network('%s/%s' % (search_ip[0], search_prefix[0])) return str(ip_obj.ip), str(ip_obj.netmask)
def test_to_xml(self): graphics = {'type': 'spice', 'listen': '127.0.0.1'} vm_uuid = str(uuid.uuid4()).replace('-', '') if os.uname()[4] in ['ppc', 'ppc64', 'ppc64le']: maxmem = 3328 else: maxmem = 3072 t = VMTemplate({'name': 'test-template', 'cdrom': self.iso, 'max_memory': maxmem << 10}) xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) self.assertEquals(vm_uuid, xpath_get_text(xml, "/domain/uuid")[0]) self.assertEquals('test-vm', xpath_get_text(xml, "/domain/name")[0]) expr = "/domain/devices/graphics/@type" self.assertEquals(graphics['type'], xpath_get_text(xml, expr)[0]) expr = "/domain/devices/graphics/@listen" self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0]) expr = "/domain/maxMemory/@slots" self.assertEquals('2', xpath_get_text(xml, expr)[0])
def lookup(self, name): pool = self.get_storagepool(name, self.conn) info = pool.info() autostart = True if pool.autostart() else False persistent = True if pool.isPersistent() else False xml = pool.XMLDesc(0) path = xpath_get_text(xml, "/pool/target/path")[0] pool_type = xpath_get_text(xml, "/pool/@type")[0] source = self._get_storage_source(pool_type, xml) # FIXME: nfs workaround - prevent any libvirt operation # for a nfs if the corresponding NFS server is down. if pool_type == 'netfs' and not self._nfs_status_online(pool): kimchi_log.debug("NFS pool %s is offline, reason: NFS " "server %s is unreachable.", name, source['addr']) # Mark state as '4' => inaccessible. info[0] = 4 # skip calculating volumes nr_volumes = 0 else: nr_volumes = self._get_storagepool_vols_num(pool) res = {'state': POOL_STATE_MAP[info[0]], 'path': path, 'source': source, 'type': pool_type, 'autostart': autostart, 'capacity': info[1], 'allocated': info[2], 'available': info[3], 'nr_volumes': nr_volumes, 'persistent': persistent} if not pool.isPersistent(): # Deal with deep scan generated pool try: with self.objstore as session: task_id = session.get('scanning', name) res['task_id'] = str(task_id) res['type'] = 'kimchi-iso' except NotFoundError: # User created normal pool pass return res
def _get_access_info(self, dom): users = groups = list() access_xml = (get_metadata_node(dom, "access", self.caps.metadata_support) or """<access></access>""") access_info = dictize(access_xml) auth = config.get("authentication", "method") if ('auth' in access_info['access'] and ('type' in access_info['access']['auth'] or len(access_info['access']['auth']) > 1)): users = xpath_get_text(access_xml, "/access/auth[@type='%s']/user" % auth) groups = xpath_get_text(access_xml, "/access/auth[@type='%s']/group" % auth) elif auth == 'pam': # Compatible to old permission tagging users = xpath_get_text(access_xml, "/access/user") groups = xpath_get_text(access_xml, "/access/group") return users, groups
def lookup(self, pool, name): vol = StorageVolumeModel.get_storagevolume(pool, name, self.conn) path = vol.path() info = vol.info() xml = vol.XMLDesc(0) try: fmt = xpath_get_text(xml, "/volume/target/format/@type")[0] except IndexError: # Not all types of libvirt storage can provide volume format # infomation. When there is no format information, we assume # it's 'raw'. fmt = 'raw' iso_img = None # 'raw' volumes from 'logical' pools may actually be 'iso'; # libvirt always reports them as 'raw' pool_info = self.storagepool.lookup(pool) if pool_info['type'] == 'logical' and fmt == 'raw': try: iso_img = IsoImage(path) except IsoFormatError: # not 'iso' afterall pass else: fmt = 'iso' used_by = get_disk_used_by(self.objstore, self.conn, path) res = dict(type=VOLUME_TYPE_MAP[info[0]], capacity=info[1], allocation=info[2], path=path, used_by=used_by, format=fmt) if fmt == 'iso': if os.path.islink(path): path = os.path.join(os.path.dirname(path), os.readlink(path)) os_distro = os_version = 'unknown' try: if iso_img is None: iso_img = IsoImage(path) os_distro, os_version = iso_img.probe() bootable = True except IsoFormatError: bootable = False res.update( dict(os_distro=os_distro, os_version=os_version, path=path, bootable=bootable)) return res
def _get_storage_source(self, pool_type, pool_xml): source = {} if pool_type not in STORAGE_SOURCES: return source for key, val in STORAGE_SOURCES[pool_type].items(): res = xpath_get_text(pool_xml, val) if len(res) == 1: source[key] = res[0] elif len(res) == 0: source[key] = "" else: source[key] = res return source
def revert(self, vm_name, name): try: vir_dom = VMModel.get_vm(vm_name, self.conn) vir_snap = self.get_vmsnapshot(vm_name, name) vir_dom.revertToSnapshot(vir_snap, 0) # get vm name recorded in the snapshot and return new uri params vm_new_name = xpath_get_text(vir_snap.getXMLDesc(0), 'domain/name')[0] return [vm_new_name, name] except libvirt.libvirtError, e: raise OperationFailed('KCHSNAP0009E', {'name': name, 'vm': vm_name, 'err': e.message})
def start(self, name): # make sure the ISO file has read permission dom = self.get_vm(name, self.conn) xml = dom.XMLDesc(0) xpath = "/domain/devices/disk[@device='cdrom']/source/@file" isofiles = xpath_get_text(xml, xpath) for iso in isofiles: run_setfacl_set_attr(iso) dom = self.get_vm(name, self.conn) try: dom.create() except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()})
def revert(self, vm_name, name): try: vir_dom = VMModel.get_vm(vm_name, self.conn) vir_snap = self.get_vmsnapshot(vm_name, name) vir_dom.revertToSnapshot(vir_snap, 0) # get vm name recorded in the snapshot and return new uri params vm_new_name = xpath_get_text(vir_snap.getXMLDesc(0), 'domain/name')[0] return [vm_new_name, name] except libvirt.libvirtError, e: raise OperationFailed('KCHSNAP0009E', { 'name': name, 'vm': vm_name, 'err': e.message })
def update(self, name, params): pool = self.get_storagepool(name, self.conn) if 'autostart' in params: if params['autostart']: pool.setAutostart(1) else: pool.setAutostart(0) if 'disks' in params: # check if pool is type 'logical' xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type != 'logical': raise InvalidOperation('KCHPOOL0029E') self._update_lvm_disks(name, params['disks']) ident = pool.name() return ident.decode('utf-8')
def activate(self, name): pool = self.get_storagepool(name, self.conn) # FIXME: nfs workaround - do not activate a NFS pool # if the NFS server is not reachable. xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type == 'netfs' and not self._nfs_status_online(pool): # block the user from activating the pool. source = self._get_storage_source(pool_type, xml) raise OperationFailed("KCHPOOL0032E", {'name': name, 'server': source['addr']}) return try: pool.create(0) except libvirt.libvirtError as e: raise OperationFailed("KCHPOOL0009E", {'name': name, 'err': e.get_error_message()})
def _nfs_status_online(self, pool, poolArgs=None): if not poolArgs: xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] source = self._get_storage_source(pool_type, xml) poolArgs = {} poolArgs['name'] = pool.name() poolArgs['type'] = pool_type poolArgs['source'] = {'path': source['path'], 'host': source['addr']} conn = self.conn.get() poolDef = StoragePoolDef.create(poolArgs) try: poolDef.prepare(conn) return True except Exception: return False
def update(self, name, params): pool = self.get_storagepool(name, self.conn) if 'autostart' in params: if params['autostart']: pool.setAutostart(1) else: pool.setAutostart(0) if 'disks' in params: # check if pool is type 'logical' xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type != 'logical': raise InvalidOperation('KCHPOOL0029E') self._update_lvm_disks(name, params['disks']) ident = pool.name() return ident
def _nfs_status_online(self, pool, poolArgs=None): if not poolArgs: xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] source = self._get_storage_source(pool_type, xml) poolArgs = {} poolArgs['name'] = pool.name() poolArgs['type'] = pool_type poolArgs['source'] = { 'path': source['path'], 'host': source['addr'] } conn = self.conn.get() poolDef = StoragePoolDef.create(poolArgs) try: poolDef.prepare(conn) return True except Exception: return False
def _get_devices_fc_host(self): conn = self.conn.get() # Libvirt < 1.0.5 does not support fc_host capability if not self.caps.fc_host_support: ret = [] scsi_hosts = self._get_devices_with_capability('scsi_host') for host in scsi_hosts: xml = conn.nodeDeviceLookupByName(host).XMLDesc(0) path = '/device/capability/capability/@type' if 'fc_host' in xpath_get_text(xml, path): ret.append(host) return ret # Double verification to catch the case where the libvirt # supports fc_host but does not, for some reason, recognize # the libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST # attribute. if not self.cap_map['fc_host']: return conn.listDevices('fc_host', 0) return self._get_devices_with_capability('fc_host')
def activate(self, name): pool = self.get_storagepool(name, self.conn) # FIXME: nfs workaround - do not activate a NFS pool # if the NFS server is not reachable. xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type == 'netfs' and not self._nfs_status_online(pool): # block the user from activating the pool. source = self._get_storage_source(pool_type, xml) raise OperationFailed("KCHPOOL0032E", { 'name': name, 'server': source['addr'] }) return try: pool.create(0) except libvirt.libvirtError as e: raise OperationFailed("KCHPOOL0009E", { 'name': name, 'err': e.get_error_message() })
def template_volume_validate(self, tmp_volumes, pool): kwargs = {'conn': self.conn, 'objstore': self.objstore} pool_type = xpath_get_text(pool.XMLDesc(0), "/pool/@type")[0] pool_name = pool.name() # as we discussion, we do not mix disks from 2 different types of # storage pools, for instance: we do not create a template with 2 # disks, where one comes from a SCSI pool and other is a .img in # a DIR pool. if pool_type in ['iscsi', 'scsi']: if not tmp_volumes: raise InvalidParameter("KCHTMPL0018E") storagevolumes = __import__("kimchi.model.storagevolumes", fromlist=['']) pool_volumes = storagevolumes.StorageVolumesModel( **kwargs).get_list(pool_name) vols = set(tmp_volumes) - set(pool_volumes) if vols: raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': vols})
def get_network_from_xml(xml): address = xpath_get_text(xml, "/network/ip/@address") address = address and address[0] or '' netmask = xpath_get_text(xml, "/network/ip/@netmask") netmask = netmask and netmask[0] or '' net = address and netmask and "/".join([address, netmask]) or '' dhcp_start = xpath_get_text(xml, "/network/ip/dhcp/range/@start") dhcp_start = dhcp_start and dhcp_start[0] or '' dhcp_end = xpath_get_text(xml, "/network/ip/dhcp/range/@end") dhcp_end = dhcp_end and dhcp_end[0] or '' dhcp = {'start': dhcp_start, 'end': dhcp_end} forward_mode = xpath_get_text(xml, "/network/forward/@mode") forward_mode = forward_mode and forward_mode[0] or '' forward_if = xpath_get_text(xml, "/network/forward/interface/@dev") forward_pf = xpath_get_text(xml, "/network/forward/pf/@dev") bridge = xpath_get_text(xml, "/network/bridge/@name") bridge = bridge and bridge[0] or '' return {'subnet': net, 'dhcp': dhcp, 'bridge': bridge, 'forward': {'mode': forward_mode, 'interface': forward_if, 'pf': forward_pf}}
def template_volume_validate(self, tmp_volumes, pool): kwargs = {'conn': self.conn, 'objstore': self.objstore} pool_type = xpath_get_text(pool.XMLDesc(0), "/pool/@type")[0] pool_name = unicode(pool.name(), 'utf-8') # as we discussion, we do not mix disks from 2 different types of # storage pools, for instance: we do not create a template with 2 # disks, where one comes from a SCSI pool and other is a .img in # a DIR pool. if pool_type in ['iscsi', 'scsi']: if not tmp_volumes: raise InvalidParameter("KCHTMPL0018E") storagevolumes = __import__("kimchi.model.storagevolumes", fromlist=['']) pool_volumes = storagevolumes.StorageVolumesModel( **kwargs).get_list(pool_name) vols = set(tmp_volumes) - set(pool_volumes) if vols: raise InvalidParameter("KCHTMPL0019E", { 'pool': pool_name, 'volume': vols })
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)
def lookup(self, name): dom = self.get_vm(name, self.conn) info = dom.info() state = DOM_STATE_MAP[info[0]] screenshot = None # (type, listen, port, passwd, passwdValidTo) graphics = self._vm_get_graphics(name) graphics_port = graphics[2] graphics_port = graphics_port if state == 'running' else None try: if state == 'running' and self._has_video(dom): screenshot = self.vmscreenshot.lookup(name) elif state == 'shutoff': # reset vm stats when it is powered off to avoid sending # incorrect (old) data stats[dom.UUIDString()] = {} except NotFoundError: pass with self.objstore as session: try: extra_info = session.get('vm', dom.UUIDString()) except NotFoundError: extra_info = {} icon = extra_info.get('icon') vm_stats = stats.get(dom.UUIDString(), {}) res = {} res['cpu_utilization'] = vm_stats.get('cpu', 0) res['net_throughput'] = vm_stats.get('net_io', 0) res['net_throughput_peak'] = vm_stats.get('max_net_io', 100) res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100) users, groups = self._get_access_info(dom) if state == 'shutoff': xml = dom.XMLDesc(0) val = xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0] unit_list = xpath_get_text(xml, XPATH_DOMAIN_MEMORY_UNIT) if len(unit_list) > 0: unit = unit_list[0] else: unit = 'KiB' memory = convert_data_size(val, unit, 'MiB') else: memory = info[2] >> 10 return { 'name': name, 'state': state, 'stats': res, 'uuid': dom.UUIDString(), 'memory': memory, 'cpus': info[3], 'screenshot': screenshot, 'icon': icon, # (type, listen, port, passwd, passwdValidTo) 'graphics': { "type": graphics[0], "listen": graphics[1], "port": graphics_port, "passwd": graphics[3], "passwdValidTo": graphics[4] }, 'users': users, 'groups': groups, 'access': 'full', 'persistent': True if dom.isPersistent() else False }
def _vm_get_networks(self, dom): xml = dom.XMLDesc(0) xpath = "/domain/devices/interface[@type='network']/source/@network" return xpath_get_text(xml, xpath)
def _get_storage_type(self): pool = self._storage_validate() xml = pool.XMLDesc(0) return xpath_get_text(xml, "/pool/@type")[0]
def _get_storage_path(self): pool = self._storage_validate() xml = pool.XMLDesc(0) return xpath_get_text(xml, "/pool/target/path")[0]
else: for s in snapshot_names: self.vmsnapshot.delete(name, s) try: dom.undefine() except libvirt.libvirtError as e: raise OperationFailed("KCHVM0021E", {'name': name, 'err': e.get_error_message()}) for path in paths: try: vol = conn.storageVolLookupByPath(path) pool = vol.storagePoolLookupByVolume() xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type not in READONLY_POOL_TYPE: vol.delete(0) # Update objstore to remove the volume with self.objstore as session: session.delete('storagevolume', path, ignore_missing=True) except libvirt.libvirtError as e: kimchi_log.error('Unable to get storage volume by path: %s' % e.message) except Exception as e: raise OperationFailed('KCHVOL0017E', {'err': e.message}) try: with self.objstore as session: if path in session.get_list('storagevolume'):
def _vm_get_disk_paths(self, dom): xml = dom.XMLDesc(0) xpath = "/domain/devices/disk[@device='disk']/source/@file" return xpath_get_text(xml, xpath)
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()