def _generate_thumbnail(self): thumbnail = os.path.join( config.get_screenshot_path(), '%s-%s.png' % (self.vm_uuid, str(uuid.uuid4()))) self._get_test_result() if stream_test_result is None: self._watch_stream_creation(thumbnail) elif stream_test_result: try: self._generate_scratch(thumbnail) except Exception: wok_log.error('screenshot_creation: Unable to create ' 'screenshot image %s.' % thumbnail) else: self._create_black_image(thumbnail) if os.path.getsize(thumbnail) == 0: self._create_black_image(thumbnail) else: im = Image.open(thumbnail) try: # Prevent Image lib from lazy load, # work around pic truncate validation in thumbnail generation im.thumbnail(self.THUMBNAIL_SIZE) except Exception as e: wok_log.warning('Image load with warning: %s.' % e) im.save(thumbnail, 'PNG') self.info['thumbnail'] = thumbnail
def _is_dev_extended_partition(devType, devNodePath): if devType != 'part': return False if devNodePath.startswith('/dev/mapper'): try: dev_maj_min = _get_dev_major_min(devNodePath.split('/')[-1]) parent_sys_path = '/sys/dev/block/' + dev_maj_min + '/slaves' parent_dm_name = os.listdir(parent_sys_path)[0] parent_maj_min = open(parent_sys_path + '/' + parent_dm_name + '/dev').readline().rstrip() diskPath = _get_dev_node_path(parent_maj_min) except Exception as e: wok_log.error('Error dealing with dev mapper device: ' + devNodePath) raise OperationFailed('GGBDISK00001E', {'err': e.message}) else: diskPath = devNodePath.rstrip('0123456789') device = PDevice(diskPath) try: extended_part = PDisk(device).getExtendedPartition() except NotImplementedError as e: wok_log.warning( 'Error getting extended partition info for dev %s type %s: %s', devNodePath, devType, e.message) # Treate disk with unsupported partiton table as if it does not # contain extended partitions. return False if extended_part and extended_part.path == devNodePath: return True return False
def _generate_thumbnail(self): thumbnail = os.path.join(config.get_screenshot_path(), '%s-%s.png' % (self.vm_uuid, str(uuid.uuid4()))) self._get_test_result() if stream_test_result is None: self._watch_stream_creation(thumbnail) elif stream_test_result: try: self._generate_scratch(thumbnail) except: wok_log.error("screenshot_creation: Unable to create " "screenshot image %s." % thumbnail) else: self._create_black_image(thumbnail) if os.path.getsize(thumbnail) == 0: self._create_black_image(thumbnail) else: im = Image.open(thumbnail) try: # Prevent Image lib from lazy load, # work around pic truncate validation in thumbnail generation im.thumbnail(self.THUMBNAIL_SIZE) except Exception as e: wok_log.warning("Image load with warning: %s." % e) im.save(thumbnail, "PNG") self.info['thumbnail'] = thumbnail
def _is_dev_extended_partition(devType, devNodePath): if devType != 'part': return False if devNodePath.startswith('/dev/mapper'): try: dev_maj_min = _get_dev_major_min(devNodePath.split("/")[-1]) parent_sys_path = '/sys/dev/block/' + dev_maj_min + '/slaves' parent_dm_name = os.listdir(parent_sys_path)[0] parent_maj_min = open( parent_sys_path + '/' + parent_dm_name + '/dev').readline().rstrip() diskPath = _get_dev_node_path(parent_maj_min) except Exception as e: wok_log.error( "Error dealing with dev mapper device: " + devNodePath) raise OperationFailed("GGBDISK00001E", {'err': e.message}) else: diskPath = devNodePath.rstrip('0123456789') device = PDevice(diskPath) try: extended_part = PDisk(device).getExtendedPartition() except NotImplementedError as e: wok_log.warning( "Error getting extended partition info for dev %s type %s: %s", devNodePath, devType, e.message) # Treate disk with unsupported partiton table as if it does not # contain extended partitions. return False if extended_part and extended_part.path == devNodePath: return True return False
def parse_hdds(temperature_unit): # hddtemp will strangely convert a non-number (see error case # below) to 32 deg F. So just always ask for C and convert later. out, error, rc = run_command(['hddtemp']) if rc: wok_log.error("Error retrieving HD temperatures: rc %s." "output: %s" % (rc, error)) return None hdds = OrderedDict() for hdd in out.splitlines(): hdd_name = '' hdd_temp = 0.0 try: hdd_items = hdd.split(':') hdd_name, hdd_temp = hdd_items[0], hdd_items[2] hdd_temp = re.sub('°[C|F]', '', hdd_temp).strip() except Exception as e: wok_log.error('Sensors hdd parse error: %s' % e.message) continue try: # Try to convert the number to a float. If it fails, # don't add this disk to the list. hdd_temp = float(hdd_temp) if(temperature_unit == 'F'): hdd_temp = 9.0/5.0 * hdd_temp + 32 hdds[hdd_name] = hdd_temp except ValueError: # If no sensor data, parse float will fail. For example: # "/dev/sda: IBM IPR-10 5D831200: S.M.A.R.T. not available" wok_log.warning("Sensors hdd: %s" % hdd) hdds['unit'] = temperature_unit return hdds
def get_list(self, storage_server, _target_type=None, _server_port=None): target_list = list() if not _target_type: target_types = STORAGE_SERVERS else: target_types = [_target_type] for target_type in target_types: if not self.caps.nfs_target_probe and target_type == 'netfs': targets = patch_find_nfs_target(storage_server) else: xml = self._get_storage_server_spec( server=storage_server, target_type=target_type, server_port=_server_port, ) conn = self.conn.get() try: ret = conn.findStoragePoolSources(target_type, xml, 0) except libvirt.libvirtError as e: wok_log.warning( f'Query storage pool source fails because of ' f'{e.get_error_message()}' ) continue targets = self._parse_target_source_result(target_type, ret) target_list.extend(targets) # Get all netfs and iscsi paths in use used_paths = [] try: conn = self.conn.get() # Get all existing ISCSI and NFS pools pools = conn.listAllStoragePools( libvirt.VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI | libvirt.VIR_CONNECT_LIST_STORAGE_POOLS_NETFS ) for pool in pools: pool_xml = pool.XMLDesc(0) root = objectify.fromstring(pool_xml) if root.get('type') == 'netfs' and root.source.dir is not None: used_paths.append(root.source.dir.get('path')) elif root.get('type') == 'iscsi' and root.source.device is not None: used_paths.append(root.source.device.get('path')) except libvirt.libvirtError as e: wok_log.warning( f'Query storage pool source fails because of {e.get_error_message()}' ) # Filter target_list to not not show the used paths target_list = [ elem for elem in target_list if elem.get('target') not in used_paths ] return [dict(t) for t in set(tuple(t.items()) for t in target_list)]
def event_enospc_cb(self, conn, dom, path, dev, action, reason, args): if reason == "enospc": info = { "vm": dom.name(), "srcPath": path, "devAlias": dev, } add_notification("KCHEVENT0004W", info, '/plugins/kimchi') msg = WokMessage("KCHEVENT0004W", info, '/plugins/kimchi') wok_log.warning(msg.get_text())
def get_list(self, storage_server, _target_type=None, _server_port=None): target_list = list() if not _target_type: target_types = STORAGE_SERVERS else: target_types = [_target_type] for target_type in target_types: if not self.caps.nfs_target_probe and target_type == 'netfs': targets = patch_find_nfs_target(storage_server) else: xml = self._get_storage_server_spec(server=storage_server, target_type=target_type, server_port=_server_port) conn = self.conn.get() try: ret = conn.findStoragePoolSources(target_type, xml, 0) except libvirt.libvirtError as e: err = "Query storage pool source fails because of %s" wok_log.warning(err, e.get_error_message()) continue targets = self._parse_target_source_result(target_type, ret) target_list.extend(targets) # Get all netfs and iscsi paths in use used_paths = [] try: conn = self.conn.get() # Get all existing ISCSI and NFS pools pools = conn.listAllStoragePools( libvirt.VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI | libvirt.VIR_CONNECT_LIST_STORAGE_POOLS_NETFS) for pool in pools: pool_xml = pool.XMLDesc(0) root = objectify.fromstring(pool_xml) if root.get('type') == 'netfs' and \ root.source.dir is not None: used_paths.append(root.source.dir.get('path')) elif root.get('type') == 'iscsi' and \ root.source.device is not None: used_paths.append(root.source.device.get('path')) except libvirt.libvirtError as e: err = "Query storage pool source fails because of %s" wok_log.warning(err, e.get_error_message()) # Filter target_list to not not show the used paths target_list = [elem for elem in target_list if elem.get('target') not in used_paths] return [dict(t) for t in set(tuple(t.items()) for t in target_list)]
def probe_img_info(path): cmd = ["qemu-img", "info", "--output=json", path] info = dict() try: out = run_command(cmd, 10)[0] except TimeoutExpired: wok_log.warning("Cannot decide format of base img %s", path) return None info = json.loads(out) info['virtual-size'] = info['virtual-size'] >> 30 info['actual-size'] = info['actual-size'] >> 30 return info
def _validate_pci_passthrough_env(): # Linux kernel < 3.5 doesn't provide /sys/kernel/iommu_groups if os.path.isdir('/sys/kernel/iommu_groups'): if not glob.glob('/sys/kernel/iommu_groups/*'): raise InvalidOperation("KCHVMHDEV0003E") # Enable virt_use_sysfs on RHEL6 and older distributions # In recent Fedora, there is no virt_use_sysfs. out, err, rc = run_command(['getsebool', 'virt_use_sysfs'], silent=True) if rc == 0 and out.rstrip('\n') != "virt_use_sysfs --> on": out, err, rc = run_command(['setsebool', '-P', 'virt_use_sysfs=on']) if rc != 0: wok_log.warning("Unable to turn on sebool virt_use_sysfs")
def _validate_pci_passthrough_env(): # Linux kernel < 3.5 doesn't provide /sys/kernel/iommu_groups if os.path.isdir('/sys/kernel/iommu_groups'): if not glob.glob('/sys/kernel/iommu_groups/*'): raise InvalidOperation('KCHVMHDEV0003E') # Enable virt_use_sysfs on RHEL6 and older distributions # In recent Fedora, there is no virt_use_sysfs. out, err, rc = run_command( ['getsebool', 'virt_use_sysfs'], silent=True) if rc == 0 and out.rstrip('\n') != 'virt_use_sysfs --> on': out, err, rc = run_command( ['setsebool', '-P', 'virt_use_sysfs=on']) if rc != 0: wok_log.warning('Unable to turn on sebool virt_use_sysfs')
def _is_dev_extended_partition(devType, devNodePath): if devType != "part": return False diskPath = devNodePath.rstrip("0123456789") device = PDevice(diskPath) try: extended_part = PDisk(device).getExtendedPartition() except NotImplementedError as e: wok_log.warning("Error getting extended partition info for dev %s type %s: %s", devNodePath, devType, e.message) # Treate disk with unsupported partiton table as if it does not # contain extended partitions. return False if extended_part and extended_part.path == devNodePath: return True return False
def _is_dev_extended_partition(devType, devNodePath): if devType != 'part': return False diskPath = devNodePath.rstrip('0123456789') device = PDevice(diskPath) try: extended_part = PDisk(device).getExtendedPartition() except NotImplementedError as e: wok_log.warning( "Error getting extended partition info for dev %s type %s: %s", devNodePath, devType, e.message) # Treate disk with unsupported partiton table as if it does not # contain extended partitions. return False if extended_part and extended_part.path == devNodePath: return True return False
def parse_hdds(temperature_unit): # hddtemp will strangely convert a non-number (see error case # below) to 32 deg F. So just always ask for C and convert later. out, error, rc = run_command(['hddtemp']) if rc: wok_log.error("Error retrieving HD temperatures: rc %s." "output: %s" % (rc, error)) return None hdds = OrderedDict() for hdd in out.splitlines(): # This error message occurs when the HDD does not # have S.M.A.R.T (Self-Monitoring, Analysis, and # Reporting Technology) available. Skip the hdd line # in this case. if 'S.M.A.R.T. not available' in hdd: continue hdd_name = '' hdd_temp = 0.0 try: hdd_items = hdd.split(':') hdd_name, hdd_temp = hdd_items[0], hdd_items[2] hdd_temp = re.sub('°[C|F]', '', hdd_temp).strip() except Exception as e: wok_log.error('Sensors hdd parse error: %s' % e.message) continue try: # Try to convert the number to a float. If it fails, # don't add this disk to the list. hdd_temp = float(hdd_temp) if(temperature_unit == 'F'): hdd_temp = 9.0/5.0 * hdd_temp + 32 hdds[hdd_name] = hdd_temp except ValueError: # If no sensor data, parse float will fail. wok_log.warning("Sensors hdd: %s" % hdd) hdds['unit'] = temperature_unit return hdds
def event_enospc_cb(self, conn, dom, path, dev, action, reason, args): if reason == 'enospc': info = {'vm': dom.name(), 'srcPath': path, 'devAlias': dev} add_notification('KCHEVENT0004W', info, '/plugins/kimchi') msg = WokMessage('KCHEVENT0004W', info, '/plugins/kimchi') wok_log.warning(msg.get_text())
def kernel_support_vfio(): out, err, rc = run_command(['modprobe', 'vfio-pci']) if rc != 0: wok_log.warning("Unable to load Kernal module vfio-pci.") return False return True
def _get_tmpl_defaults(): """ ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf) {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} The default values should be like below: {'main': {'networks': ['default']}, 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, 'graphics': {'type': 'spice', 'listen': '127.0.0.1'}} The default values on s390x architecture: {'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, 'graphics': {'type': 'spice', 'listen': '127.0.0.1'}} """ # Create dict with default values tmpl_defaults = defaultdict(dict) host_arch = _get_arch() tmpl_defaults['main']['networks'] = ['default'] if host_arch in ['s390x', 's390']: tmpl_defaults['main']['networks'] = [] tmpl_defaults['memory'] = { 'current': _get_default_template_mem(), 'maxmemory': _get_default_template_mem(), } tmpl_defaults['storage']['disk.0'] = { 'size': 10, 'format': 'qcow2', 'pool': 'default', } is_on_s390x = True if _get_arch() == 's390x' else False if is_on_s390x: tmpl_defaults['storage']['disk.0']['path'] = '/var/lib/libvirt/images/' del tmpl_defaults['storage']['disk.0']['pool'] tmpl_defaults['processor']['vcpus'] = 1 tmpl_defaults['processor']['maxvcpus'] = 1 tmpl_defaults['graphics'] = {'type': 'vnc', 'listen': '127.0.0.1'} default_config = ConfigObj(tmpl_defaults) # Load template configuration file config_file = os.path.join(kimchiPaths.sysconf_dir, 'template.conf') config = ConfigObj(config_file) # File configuration takes preference. # In s390x, file configuration can have storage pool or path. # Default configuration for s390x is storage path. # In case file conf has storage pool then storage pool takes preference. # When conf file has explicitly storage pool: "defaults" should # have storage pool and default configured path should be removed, # as either storage can be path or pool, cannot be both. # When conf file does not explicity storage pool or have explicitly # storage path: "default" should have storage path only and cannot # have default pool. # # Check file conf has storage configured. if is_on_s390x and config.get('storage').get('disk.0'): # remove storage from default_config as file configuration takes # preference. default_config.pop('storage') # Get storage configuration present in conf file config_pool = config.get('storage').get('disk.0').get('pool') config_path = config.get('storage').get('disk.0').get('path') # If storage configured in conf file then it should have either # pool or path. if not config_pool and not config_path: raise InvalidParameter('KCHTMPL0040E') # On s390x if config file has both path and pool uncommented # then path should take preference. if config_pool and config_path: wok_log.warning('Both default pool and path are specified in' + ' template.conf. Hence default pool is being' + ' ignored and only default path will be used') config.get('storage').get('disk.0').pop('pool') # Merge default configuration with file configuration default_config.merge(config) # Create a dict with default values according to data structure # expected by VMTemplate defaults = { 'domain': 'kvm', 'arch': os.uname()[4], 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2', } # Parse main section to get networks and memory values defaults.update(default_config.pop('main')) defaults['memory'] = default_config.pop('memory') defaults['memory']['current'] = int(defaults['memory']['current']) defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory']) # for s390x architecture, set default console as virtio if is_on_s390x: defaults['console'] = 'virtio' # Parse storage section to get disks values storage_section = default_config.pop('storage') defaults['disks'] = [] for index, disk in enumerate(storage_section.keys()): data = storage_section[disk] data['index'] = int(disk.split('.')[1]) # Right now 'Path' is only supported on s390x if storage_section[disk].get('path') and is_on_s390x: data['path'] = storage_section[disk].pop('path') if 'size' not in storage_section[disk]: data['size'] = tmpl_defaults['storage']['disk.0']['size'] else: data['size'] = storage_section[disk].pop('size') if 'format' not in storage_section[disk]: data['format'] = tmpl_defaults['storage']['disk.0']['format'] else: data['format'] = storage_section[disk].pop('format') else: data['pool'] = { 'name': '/plugins/kimchi/storagepools/' + storage_section[disk].pop('pool') } defaults['disks'].append(data) # Parse processor section to get vcpus and cpu_topology values processor_section = default_config.pop('processor') defaults['cpu_info'] = { 'vcpus': processor_section.pop('vcpus'), 'maxvcpus': processor_section.pop('maxvcpus'), } if len(processor_section.keys()) > 0: defaults['cpu_info']['topology'] = processor_section # Update defaults values with graphics values defaults['graphics'] = default_config.pop('graphics') # Setting default memory device slots defaults['mem_dev_slots'] = MEM_DEV_SLOTS.get(os.uname()[4], 256) return defaults
def log_error(e): wok_log = logging.getLogger('Model') wok_log.warning('Exception in generating debug file: %s', e)
def kernel_support_vfio(): out, err, rc = run_command(['modprobe', 'vfio-pci']) if rc != 0: wok_log.warning('Unable to load Kernal module vfio-pci.') return False return True
def log_error(e): wok_log = logging.getLogger("Model") wok_log.warning("Exception in generating debug file: %s", e)
def _get_tmpl_defaults(): """ ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf) {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} The default values should be like below: {'main': {'networks': ['default']}, 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, 'graphics': {'type': 'spice', 'listen': '127.0.0.1'}} The default values on s390x architecture: {'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, 'graphics': {'type': 'spice', 'listen': '127.0.0.1'}} """ # Create dict with default values tmpl_defaults = defaultdict(dict) host_arch = _get_arch() tmpl_defaults['main']['networks'] = ['default'] if host_arch in ['s390x', 's390']: tmpl_defaults['main']['networks'] = [] tmpl_defaults['memory'] = {'current': _get_default_template_mem(), 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} is_on_s390x = True if _get_arch() == 's390x' else False if is_on_s390x: tmpl_defaults['storage']['disk.0']['path'] = '/var/lib/libvirt/images/' del tmpl_defaults['storage']['disk.0']['pool'] tmpl_defaults['processor']['vcpus'] = 1 tmpl_defaults['processor']['maxvcpus'] = 1 tmpl_defaults['graphics'] = {'type': 'vnc', 'listen': '127.0.0.1'} default_config = ConfigObj(tmpl_defaults) # Load template configuration file config_file = os.path.join(kimchiPaths.sysconf_dir, 'template.conf') config = ConfigObj(config_file) # File configuration takes preference. # In s390x, file configuration can have storage pool or path. # Default configuration for s390x is storage path. # In case file conf has storage pool then storage pool takes preference. # When conf file has explicitly storage pool: "defaults" should # have storage pool and default configured path should be removed, # as either storage can be path or pool, cannot be both. # When conf file does not explicity storage pool or have explicitly # storage path: "default" should have storage path only and cannot # have default pool. # # Check file conf has storage configured. if is_on_s390x and config.get('storage').get('disk.0'): # remove storage from default_config as file configuration takes # preference. default_config.pop('storage') # Get storage configuration present in conf file config_pool = config.get('storage').get('disk.0').get('pool') config_path = config.get('storage').get('disk.0').get('path') # If storage configured in conf file then it should have either # pool or path. if not config_pool and not config_path: raise InvalidParameter('KCHTMPL0040E') # On s390x if config file has both path and pool uncommented # then path should take preference. if config_pool and config_path: wok_log.warning("Both default pool and path are specified in" + " template.conf. Hence default pool is being" + " ignored and only default path will be used") config.get('storage').get('disk.0').pop('pool') # Merge default configuration with file configuration default_config.merge(config) # Create a dict with default values according to data structure # expected by VMTemplate defaults = {'domain': 'kvm', 'arch': os.uname()[4], 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'} # Parse main section to get networks and memory values defaults.update(default_config.pop('main')) defaults['memory'] = default_config.pop('memory') defaults['memory']['current'] = int(defaults['memory']['current']) defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory']) # for s390x architecture, set default console as virtio if is_on_s390x: defaults['console'] = 'virtio' # Parse storage section to get disks values storage_section = default_config.pop('storage') defaults['disks'] = [] for index, disk in enumerate(storage_section.keys()): data = storage_section[disk] data['index'] = int(disk.split('.')[1]) # Right now 'Path' is only supported on s390x if storage_section[disk].get('path') and is_on_s390x: data['path'] = storage_section[disk].pop('path') if 'size' not in storage_section[disk]: data['size'] = tmpl_defaults['storage']['disk.0']['size'] else: data['size'] = storage_section[disk].pop('size') if 'format' not in storage_section[disk]: data['format'] = tmpl_defaults['storage']['disk.0']['format'] else: data['format'] = storage_section[disk].pop('format') else: data['pool'] = {"name": '/plugins/kimchi/storagepools/' + storage_section[disk].pop('pool')} defaults['disks'].append(data) # Parse processor section to get vcpus and cpu_topology values processor_section = default_config.pop('processor') defaults['cpu_info'] = {'vcpus': processor_section.pop('vcpus'), 'maxvcpus': processor_section.pop('maxvcpus')} if len(processor_section.keys()) > 0: defaults['cpu_info']['topology'] = processor_section # Update defaults values with graphics values defaults['graphics'] = default_config.pop('graphics') # Setting default memory device slots defaults['mem_dev_slots'] = MEM_DEV_SLOTS.get(os.uname()[4], 256) return defaults